Merge "Make checkboxes not clickable" into ub-contactsdialer-h-dev
diff --git a/res/layout/fragment_sim_import.xml b/res/layout/fragment_sim_import.xml
index d6d3acf..f848cef 100644
--- a/res/layout/fragment_sim_import.xml
+++ b/res/layout/fragment_sim_import.xml
@@ -56,7 +56,7 @@
             android:background="?android:colorBackground"
             android:minHeight="48dp"
             android:orientation="horizontal"
-            android:paddingEnd="32dp"
+            android:paddingEnd="16dp"
             android:paddingStart="16dp">
 
             <ImageView
@@ -83,8 +83,8 @@
                 android:id="@+id/account_expander_icon"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_marginRight="9dp"
                 android:layout_gravity="center_vertical"
+                android:layout_marginEnd="10dp"
                 android:scaleType="center"
                 android:src="@drawable/ic_arrow_drop_down_black_24dp"
                 android:tint="?android:textColorSecondary"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index d603973..de9764d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -458,4 +458,9 @@
     <dimen name="call_subject_dialog_secondary_text_size">14sp</dimen>
     <!-- Row padding for call subject history items. -->
     <dimen name="call_subject_history_item_padding">15dp</dimen>
+
+    <!-- Padding between SIM checkbox and end of row -->
+    <dimen name="sim_import_checkbox_end_padding">16dp</dimen>
+
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9a1dd19..250870f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -193,7 +193,7 @@
     <string name="contacts_deleted_two_named_toast"><xliff:g id="name">%1$s</xliff:g> and <xliff:g id="name">%2$s</xliff:g> deleted</string>
 
     <!-- Toast shown with names after user selected contacts are deleted by user action. [CHAR LIMIT=50] -->
-    <string name="contacts_deleted_many_named_toast"><xliff:g id="name">%1$s</xliff:g>, <xliff:g id="name">%2$s</xliff:g>, <xliff:g id="name">%3$s</xliff:g>... deleted</string>
+    <string name="contacts_deleted_many_named_toast"><xliff:g id="name">%1$s</xliff:g>, <xliff:g id="name">%2$s</xliff:g>, <xliff:g id="name">%3$s</xliff:g>\u2026 deleted</string>
 
     <!-- Toast shown after contacts that the user has selected are deleted by a user action. [CHAR LIMIT=30] -->
     <plurals name="contacts_deleted_toast">
diff --git a/src/com/android/contacts/ContactsDrawerActivity.java b/src/com/android/contacts/ContactsDrawerActivity.java
index 79d0fe2..b786940 100644
--- a/src/com/android/contacts/ContactsDrawerActivity.java
+++ b/src/com/android/contacts/ContactsDrawerActivity.java
@@ -24,6 +24,7 @@
 import android.graphics.Color;
 import android.graphics.PorterDuff;
 import android.os.Bundle;
+import android.os.Handler;
 import android.provider.ContactsContract.Intents;
 import android.support.annotation.LayoutRes;
 import android.support.design.widget.NavigationView;
@@ -112,11 +113,11 @@
     private static final String KEY_NEW_GROUP_ACCOUNT = "newGroupAccount";
     private static final String KEY_CONTACTS_VIEW = "contactsView";
 
+    private static final long DRAWER_CLOSE_DELAY = 300L;
+
     protected ContactsView mCurrentView;
 
     private class ContactsActionBarDrawerToggle extends ActionBarDrawerToggle {
-
-        private Runnable mRunnable;
         private boolean mMenuClickedBefore = SharedPreferenceUtil.getHamburgerMenuClickedBefore(
                 ContactsDrawerActivity.this);
 
@@ -177,16 +178,8 @@
             if (newState != DrawerLayout.STATE_IDLE) {
                 updateStatusBarBackground();
             }
-            if (mRunnable != null && newState == DrawerLayout.STATE_IDLE) {
-                mRunnable.run();
-                mRunnable = null;
-            }
             initializeAssistantNewBadge();
         }
-
-        public void runWhenIdle(Runnable runnable) {
-            mRunnable = runnable;
-        }
     }
 
     protected ContactListFilterController mContactListFilterController;
@@ -437,14 +430,9 @@
                 menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                     @Override
                     public boolean onMenuItemClick(MenuItem item) {
-                        mToggle.runWhenIdle(new Runnable() {
-                            @Override
-                            public void run() {
-                                onGroupMenuItemClicked(groupListItem.getGroupId(),
-                                        groupListItem.getTitle());
-                                updateMenuSelection(menuItem);
-                            }
-                        });
+                        onGroupMenuItemClicked(groupListItem.getGroupId(),
+                                groupListItem.getTitle());
+                        updateMenuSelection(menuItem);
                         mDrawer.closeDrawer(GravityCompat.START);
                         return true;
                     }
@@ -464,12 +452,7 @@
         menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
             @Override
             public boolean onMenuItemClick(MenuItem item) {
-                mToggle.runWhenIdle(new Runnable() {
-                    @Override
-                    public void run() {
-                        onCreateGroupMenuItemClicked();
-                    }
-                });
+                onCreateGroupMenuItemClicked();
                 mDrawer.closeDrawer(GravityCompat.START);
                 return true;
             }
@@ -562,13 +545,8 @@
             menuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                 @Override
                 public boolean onMenuItemClick(MenuItem item) {
-                    mToggle.runWhenIdle(new Runnable() {
-                        @Override
-                        public void run() {
-                            onFilterMenuItemClicked(intent);
-                            updateMenuSelection(menuItem);
-                        }
-                    });
+                    onFilterMenuItemClicked(intent);
+                    updateMenuSelection(menuItem);
                     mDrawer.closeDrawer(GravityCompat.START);
                     return true;
                 }
@@ -614,28 +592,29 @@
     @Override
     public boolean onNavigationItemSelected(final MenuItem item) {
         final int id = item.getItemId();
-        mToggle.runWhenIdle(new Runnable() {
-            @Override
-            public void run() {
-                if (id == R.id.nav_settings) {
+
+        if (id == R.id.nav_settings) {
+            new Handler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
                     startActivity(createPreferenceIntent());
-                } else if (id == R.id.nav_help) {
-                    HelpUtils.launchHelpAndFeedbackForMainScreen(ContactsDrawerActivity.this);
-                } else if (id == R.id.nav_all_contacts) {
-                    switchToAllContacts();
-                } else if (id == R.id.nav_assistant) {
-                    if (!isAssistantView()) {
-                        launchAssistant();
-                        updateMenuSelection(item);
-                    }
-                } else if (item.getIntent() != null) {
-                    ImplicitIntentsUtil.startActivityInApp(ContactsDrawerActivity.this,
-                            item.getIntent());
-                } else {
-                    Log.w(TAG, "Unhandled navigation view item selection");
                 }
+            }, DRAWER_CLOSE_DELAY);
+        } else if (id == R.id.nav_help) {
+            HelpUtils.launchHelpAndFeedbackForMainScreen(ContactsDrawerActivity.this);
+        } else if (id == R.id.nav_all_contacts) {
+            switchToAllContacts();
+        } else if (id == R.id.nav_assistant) {
+            if (!isAssistantView()) {
+                launchAssistant();
+                updateMenuSelection(item);
             }
-        });
+        } else if (item.getIntent() != null) {
+            ImplicitIntentsUtil.startActivityInApp(ContactsDrawerActivity.this,
+                    item.getIntent());
+        } else {
+            Log.w(TAG, "Unhandled navigation view item selection");
+        }
 
         mDrawer.closeDrawer(GravityCompat.START);
         return true;
diff --git a/src/com/android/contacts/DynamicShortcuts.java b/src/com/android/contacts/DynamicShortcuts.java
index 52307e4..49cc722 100644
--- a/src/com/android/contacts/DynamicShortcuts.java
+++ b/src/com/android/contacts/DynamicShortcuts.java
@@ -215,9 +215,14 @@
         final List<ShortcutInfo> result = new ArrayList<>();
 
         try {
-            // For some reason the limit query parameter is ignored for the strequent content uri
-            for (int i = 0; i < MAX_SHORTCUTS && cursor.moveToNext(); i++) {
-                result.add(createShortcutFromRow(cursor));
+            int i = 0;
+            while (i < MAX_SHORTCUTS && cursor.moveToNext()) {
+                final ShortcutInfo shortcut = createShortcutFromRow(cursor);
+                if (shortcut == null) {
+                    continue;
+                }
+                result.add(shortcut);
+                i++;
             }
         } finally {
             cursor.close();
@@ -229,6 +234,9 @@
     @VisibleForTesting
     ShortcutInfo createShortcutFromRow(Cursor cursor) {
         final ShortcutInfo.Builder builder = builderForContactShortcut(cursor);
+        if (builder == null) {
+            return null;
+        }
         addIconForContact(cursor, builder);
         return builder.build();
     }
@@ -243,6 +251,9 @@
 
     @VisibleForTesting
     ShortcutInfo.Builder builderForContactShortcut(long id, String lookupKey, String displayName) {
+        if (lookupKey == null || displayName == null) {
+            return null;
+        }
         final PersistableBundle extras = new PersistableBundle();
         extras.putLong(Contacts._ID, id);
 
@@ -392,8 +403,6 @@
                         JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS))
                 .setTriggerContentUpdateDelay(mContentChangeMinUpdateDelay)
                 .setTriggerContentMaxDelay(mContentChangeMaxUpdateDelay)
-                // Minimize impact on UX by waiting for idle before updating.
-                .setRequiresDeviceIdle(true)
                 .build();
         mJobScheduler.schedule(job);
     }
diff --git a/src/com/android/contacts/SimImportFragment.java b/src/com/android/contacts/SimImportFragment.java
index 4da4086..a873842 100644
--- a/src/com/android/contacts/SimImportFragment.java
+++ b/src/com/android/contacts/SimImportFragment.java
@@ -320,9 +320,12 @@
         private AccountWithDataSet mSelectedAccount;
         private Map<AccountWithDataSet, Set<SimContact>> mExistingMap;
         private Map<AccountWithDataSet, TreeSet<Long>> mPerAccountCheckedIds = new ArrayMap<>();
+        private final int mCheckboxPaddingEnd;
 
         public SimContactAdapter(Context context) {
             super(context);
+            mCheckboxPaddingEnd = context.getResources()
+                    .getDimensionPixelOffset(R.dimen.sim_import_checkbox_end_padding);
         }
 
         @Override
@@ -339,6 +342,10 @@
             // clickable
             contactView.getCheckBox().setFocusable(false);
             contactView.getCheckBox().setClickable(false);
+            // The default list pads the checkbox by a larger amount than we want.
+            contactView.setPaddingRelative(contactView.getPaddingStart(),
+                    contactView.getPaddingTop(), mCheckboxPaddingEnd,
+                    contactView.getPaddingBottom());
             setViewEnabled(contactView, !existsInCurrentAccount(position));
         }
 
diff --git a/src/com/android/contacts/common/vcard/ImportVCardActivity.java b/src/com/android/contacts/common/vcard/ImportVCardActivity.java
index d7b64b4..e5fab4e 100644
--- a/src/com/android/contacts/common/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/common/vcard/ImportVCardActivity.java
@@ -290,7 +290,12 @@
             } finally {
                 Log.i(LOG_TAG, "Finished caching vCard.");
                 mWakeLock.release();
-                unbindService(mConnection);
+                try {
+                    unbindService(mConnection);
+                } catch (IllegalArgumentException e) {
+                    FeedbackHelper.sendFeedback(ImportVCardActivity.this, LOG_TAG,
+                            "Cannot unbind service connection", e);
+                }
                 mProgressDialogForCachingVCard.dismiss();
                 mProgressDialogForCachingVCard = null;
                 finish();
diff --git a/src/com/android/contacts/list/MultiSelectEmailAddressesListAdapter.java b/src/com/android/contacts/list/MultiSelectEmailAddressesListAdapter.java
index af9ee52..a71e49b 100644
--- a/src/com/android/contacts/list/MultiSelectEmailAddressesListAdapter.java
+++ b/src/com/android/contacts/list/MultiSelectEmailAddressesListAdapter.java
@@ -29,6 +29,7 @@
 
 import android.provider.ContactsContract.CommonDataKinds.Email;
 
+import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.list.ContactListItemView;
 import com.android.contacts.common.list.MultiSelectEntryContactListAdapter;
 import com.android.contacts.common.preference.ContactsPreferences;
@@ -162,9 +163,7 @@
         bindViewId(view, cursor, EmailQuery.EMAIL_ID);
         if (isFirstEntry) {
             bindName(view, cursor);
-            bindQuickContact(view, partition, cursor, EmailQuery.PHOTO_ID,
-                    EmailQuery.PHOTO_URI, EmailQuery.CONTACT_ID,
-                    EmailQuery.LOOKUP_KEY, EmailQuery.DISPLAY_NAME);
+            bindPhoto(view, cursor);
         } else {
             unbindName(view);
             view.removePhotoView(true, false);
@@ -172,6 +171,17 @@
         bindEmailAddress(view, cursor);
     }
 
+    protected void bindPhoto(final ContactListItemView view, Cursor cursor) {
+        final long photoId = cursor.isNull(EmailQuery.PHOTO_ID)
+                ? 0 : cursor.getLong(EmailQuery.PHOTO_ID);
+        final ContactPhotoManager.DefaultImageRequest imageRequest = photoId == 0
+                ? getDefaultImageRequestFromCursor(cursor, EmailQuery.DISPLAY_NAME,
+                EmailQuery.LOOKUP_KEY)
+                : null;
+        getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false, getCircularPhotos(),
+                imageRequest);
+    }
+
     protected void unbindName(final ContactListItemView view) {
         view.hideDisplayName();
     }
diff --git a/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java b/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java
index 985809b..5dc9c17 100644
--- a/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java
+++ b/src/com/android/contacts/list/MultiSelectPhoneNumbersListAdapter.java
@@ -29,6 +29,7 @@
 
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 
+import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.list.ContactListItemView;
 import com.android.contacts.common.list.MultiSelectEntryContactListAdapter;
 import com.android.contacts.common.preference.ContactsPreferences;
@@ -162,9 +163,7 @@
         bindViewId(view, cursor, PhoneQuery.PHONE_ID);
         if (isFirstEntry) {
             bindName(view, cursor);
-            bindQuickContact(view, partition, cursor, PhoneQuery.PHOTO_ID,
-                        PhoneQuery.PHOTO_URI, PhoneQuery.CONTACT_ID,
-                        PhoneQuery.LOOKUP_KEY, PhoneQuery.DISPLAY_NAME);
+            bindPhoto(view, cursor);
         } else {
             unbindName(view);
             view.removePhotoView(true, false);
@@ -172,6 +171,17 @@
         bindPhoneNumber(view, cursor);
     }
 
+    protected void bindPhoto(final ContactListItemView view, Cursor cursor) {
+        final long photoId = cursor.isNull(PhoneQuery.PHOTO_ID)
+                ? 0 : cursor.getLong(PhoneQuery.PHOTO_ID);
+        final ContactPhotoManager.DefaultImageRequest imageRequest = photoId == 0
+                ? getDefaultImageRequestFromCursor(cursor, PhoneQuery.DISPLAY_NAME,
+                PhoneQuery.LOOKUP_KEY)
+                : null;
+        getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false, getCircularPhotos(),
+                imageRequest);
+    }
+
     protected void unbindName(final ContactListItemView view) {
         view.hideDisplayName();
     }
diff --git a/tests/src/com/android/contacts/DynamicShortcutsTests.java b/tests/src/com/android/contacts/DynamicShortcutsTests.java
index 106b72a..34cd8c4 100644
--- a/tests/src/com/android/contacts/DynamicShortcutsTests.java
+++ b/tests/src/com/android/contacts/DynamicShortcutsTests.java
@@ -100,6 +100,14 @@
         assertEquals(1l, shortcut.getExtras().getLong(Contacts._ID));
     }
 
+    public void test_builderForContactShortcut_returnsNullWhenNameIsNull() {
+        final DynamicShortcuts sut = createDynamicShortcuts();
+
+        final ShortcutInfo.Builder shortcut = sut.builderForContactShortcut(1l, "lookup_key", null);
+
+        assertNull(shortcut);
+    }
+
     public void test_builderForContactShortcut_ellipsizesLongNamesForLabels() {
         final DynamicShortcuts sut = createDynamicShortcuts();
         sut.setShortLabelMaxLength(5);
@@ -183,6 +191,43 @@
         assertThat(arg.get(2), isShortcutForContact(3l, "starred_2", "Starred Two"));
     }
 
+    public void test_refresh_skipsContactsWithNullName() {
+        final ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
+        when(mockShortcutManager.getPinnedShortcuts()).thenReturn(
+                Collections.<ShortcutInfo>emptyList());
+        final DynamicShortcuts sut = createDynamicShortcuts(resolverWithExpectedQueries(
+                queryFor(Contacts.CONTENT_STREQUENT_URI,
+                        1l, "key1", "first",
+                        2l, "key2", "second",
+                        3l, "key3", null,
+                        4l, null, null,
+                        5l, "key5", "fifth",
+                        6l, "key6", "sixth")), mockShortcutManager);
+
+        sut.refresh();
+
+        final ArgumentCaptor<List<ShortcutInfo>> updateArgs =
+                ArgumentCaptor.forClass((Class) List.class);
+
+        verify(mockShortcutManager).setDynamicShortcuts(updateArgs.capture());
+
+        final List<ShortcutInfo> arg = updateArgs.getValue();
+        assertThat(arg.size(), equalTo(3));
+        assertThat(arg.get(0), isShortcutForContact(1l, "key1", "first"));
+        assertThat(arg.get(1), isShortcutForContact(2l, "key2", "second"));
+        assertThat(arg.get(2), isShortcutForContact(5l, "key5", "fifth"));
+
+
+        // Also verify that it doesn't crash if there are fewer than 3 valid strequent contacts
+        createDynamicShortcuts(resolverWithExpectedQueries(
+                queryFor(Contacts.CONTENT_STREQUENT_URI,
+                        1l, "key1", "first",
+                        2l, "key2", "second",
+                        3l, "key3", null,
+                        4l, null, null)), mock(ShortcutManager.class)).refresh();
+    }
+
+
     public void test_handleFlagDisabled_stopsJob() {
         final ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
         final JobScheduler mockJobScheduler = mock(JobScheduler.class);