Merge "Fix QuickContact header color on avatar removal" into ub-contactsdialer-h-dev
diff --git a/res/menu/view_group.xml b/res/menu/view_group.xml
index 1bbdd86..dcabb13 100644
--- a/res/menu/view_group.xml
+++ b/res/menu/view_group.xml
@@ -24,11 +24,6 @@
         contacts:showAsAction="ifRoom" />
 
     <item
-        android:id="@+id/menu_edit_group"
-        android:icon="@drawable/ic_create_24dp"
-        android:title="@string/menu_editGroup" />
-
-    <item
         android:id="@+id/menu_multi_send_email"
         android:title="@string/menu_sendEmailOption" />
 
@@ -37,6 +32,11 @@
         android:title="@string/menu_sendMessageOption" />
 
     <item
+        android:id="@+id/menu_edit_group"
+        android:icon="@drawable/ic_create_24dp"
+        android:title="@string/menu_editGroup" />
+
+    <item
         android:id="@+id/menu_rename_group"
         android:title="@string/menu_renameGroup"/>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3afec1e..015d612 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -175,13 +175,13 @@
     <!-- Toast shown after two contacts have been linked by a user action without a name. [CHAR LIMIT=NONE] -->
     <string name="contactsJoinedMessage">Contacts linked</string>
 
-    <!-- Toast shown with names after user selected contacts are deleted by user action. [CHAR LIMIT=30] -->
+    <!-- Toast shown with names after user selected contacts are deleted by user action. [CHAR LIMIT=50] -->
     <string name="contacts_deleted_one_named_toast"><xliff:g id="name">%1$s</xliff:g> deleted</string>
 
-    <!-- Toast shown with names after user selected contacts are deleted by user action. [CHAR LIMIT=30] -->
+    <!-- Toast shown with names after user selected contacts are deleted by user action. [CHAR LIMIT=50] -->
     <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=30] -->
+    <!-- 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>
 
     <!-- Toast shown after contacts that the user has selected are deleted by a user action. [CHAR LIMIT=30] -->
@@ -1378,6 +1378,9 @@
     <!-- Action string for selecting a .vcf file to import contacts from [CHAR LIMIT=30] -->
     <string name="import_from_vcf_file" product="default">Import from .vcf file</string>
 
+    <!-- Dialog message asking the user for confirmation before starting to import contacts from a .vcf file. [CHAR LIMIT=NONE] -->
+    <string name="import_from_vcf_file_confirmation_message" product="default">Import contacts from vCard?</string>
+
     <!-- Message shown in a Dialog confirming a user's cancel request toward existing vCard import.
          The argument is file name for the vCard import the user wants to cancel.
          [CHAR LIMIT=128] -->
diff --git a/src/com/android/contacts/DynamicShortcuts.java b/src/com/android/contacts/DynamicShortcuts.java
index f5363f6..fce2f8b 100644
--- a/src/com/android/contacts/DynamicShortcuts.java
+++ b/src/com/android/contacts/DynamicShortcuts.java
@@ -476,7 +476,7 @@
     }
 
     public static void reportShortcutUsed(Context context, String lookupKey) {
-        if (!CompatUtils.isLauncherShortcutCompatible()) return;
+        if (!CompatUtils.isLauncherShortcutCompatible() || lookupKey == null) return;
         final ShortcutManager shortcutManager = (ShortcutManager) context
                 .getSystemService(Context.SHORTCUT_SERVICE);
         shortcutManager.reportShortcutUsed(lookupKey);
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 172015b..513109c 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -280,12 +280,13 @@
             }
             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Received group URI " + mGroupUri);
             switchView(ContactsView.GROUP_VIEW);
+            mMembersFragment.toastForSaveAction(intent.getAction());
             return;
         }
 
         if (isGroupDeleteAction(intent.getAction())) {
-            toast(R.string.groupDeletedToast);
             popSecondLevel();
+            mMembersFragment.toastForSaveAction(intent.getAction());
             mCurrentView = ContactsView.ALL_CONTACTS;
             showFabWithAnimation(/* showFab */ true);
             return;
@@ -300,6 +301,7 @@
             }
             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "Received group URI " + mGroupUri);
             switchView(ContactsView.GROUP_VIEW);
+            mMembersFragment.toastForSaveAction(intent.getAction());
         }
 
         setIntent(intent);
diff --git a/src/com/android/contacts/common/activity/RequestImportVCardPermissionsActivity.java b/src/com/android/contacts/common/activity/RequestImportVCardPermissionsActivity.java
index df98d12..5bddc77 100644
--- a/src/com/android/contacts/common/activity/RequestImportVCardPermissionsActivity.java
+++ b/src/com/android/contacts/common/activity/RequestImportVCardPermissionsActivity.java
@@ -43,9 +43,11 @@
      * to prompt the user for these permissions. Moreover, finish the current activity.
      *
      * This is designed to be called inside {@link android.app.Activity#onCreate}
+     *
+     * @param isCallerSelf whether the vcard import was started from the contacts app itself.
      */
-    public static boolean startPermissionActivity(Activity activity) {
-        return startPermissionActivity(activity, REQUIRED_PERMISSIONS,
+    public static boolean startPermissionActivity(Activity activity, boolean isCallerSelf) {
+        return startPermissionActivity(activity, REQUIRED_PERMISSIONS, isCallerSelf,
                 RequestImportVCardPermissionsActivity.class);
     }
 }
\ No newline at end of file
diff --git a/src/com/android/contacts/common/activity/RequestPermissionsActivity.java b/src/com/android/contacts/common/activity/RequestPermissionsActivity.java
index 4bbac73..7e75223 100644
--- a/src/com/android/contacts/common/activity/RequestPermissionsActivity.java
+++ b/src/com/android/contacts/common/activity/RequestPermissionsActivity.java
@@ -76,7 +76,11 @@
         if (permissions != null && permissions.length > 0
                 && isAllGranted(permissions, grantResults)) {
             mPreviousActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-            startActivity(mPreviousActivityIntent);
+            if (mIsCallerSelf) {
+                startActivityForResult(mPreviousActivityIntent, 0);
+            } else {
+                startActivity(mPreviousActivityIntent);
+            }
             finish();
             overridePendingTransition(0, 0);
 
diff --git a/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java b/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java
index 999c545..ef591a5 100644
--- a/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java
+++ b/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java
@@ -45,6 +45,8 @@
     protected static final String EXTRA_STARTED_PERMISSIONS_ACTIVITY =
             "started_permissions_activity";
 
+    protected static final String EXTRA_IS_CALLER_SELF = "is_caller_self";
+
     private static final int PERMISSIONS_REQUEST_ALL_PERMISSIONS = 1;
 
     /**
@@ -55,10 +57,14 @@
 
     protected Intent mPreviousActivityIntent;
 
+    /** If true then start the target activity "for result" after permissions are granted. */
+    protected boolean mIsCallerSelf;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mPreviousActivityIntent = (Intent) getIntent().getExtras().get(PREVIOUS_ACTIVITY_INTENT);
+        mIsCallerSelf = getIntent().getBooleanExtra(EXTRA_IS_CALLER_SELF, false);
 
         // Only start a requestPermissions() flow when first starting this activity the first time.
         // The process is likely to be restarted during the permission flow (necessary to enable
@@ -76,10 +82,17 @@
      */
     protected static boolean startPermissionActivity(Activity activity,
             String[] requiredPermissions, Class<?> newActivityClass) {
+        return startPermissionActivity(activity, requiredPermissions, /* isCallerSelf */ false,
+                newActivityClass);
+    }
+
+    protected static boolean startPermissionActivity(Activity activity,
+                String[] requiredPermissions, boolean isCallerSelf, Class<?> newActivityClass) {
         if (!hasPermissions(activity, requiredPermissions)) {
             final Intent intent = new Intent(activity,  newActivityClass);
             activity.getIntent().putExtra(EXTRA_STARTED_PERMISSIONS_ACTIVITY, true);
             intent.putExtra(PREVIOUS_ACTIVITY_INTENT, activity.getIntent());
+            intent.putExtra(EXTRA_IS_CALLER_SELF, isCallerSelf);
             activity.startActivity(intent);
             activity.finish();
             return true;
diff --git a/src/com/android/contacts/common/util/AccountSelectionUtil.java b/src/com/android/contacts/common/util/AccountSelectionUtil.java
index 4dc6ec9..9fca64d 100644
--- a/src/com/android/contacts/common/util/AccountSelectionUtil.java
+++ b/src/com/android/contacts/common/util/AccountSelectionUtil.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.common.util;
 
+import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
@@ -53,53 +54,53 @@
     public static class AccountSelectedListener
             implements DialogInterface.OnClickListener {
 
-        final private Context mContext;
+        final private Activity mActivity;
         final private int mResId;
         final private int mSubscriptionId;
 
         final protected List<AccountWithDataSet> mAccountList;
 
-        public AccountSelectedListener(Context context, List<AccountWithDataSet> accountList,
+        public AccountSelectedListener(Activity activity, List<AccountWithDataSet> accountList,
                 int resId, int subscriptionId) {
             if (accountList == null || accountList.size() == 0) {
                 Log.e(LOG_TAG, "The size of Account list is 0.");
             }
-            mContext = context;
+            mActivity = activity;
             mAccountList = accountList;
             mResId = resId;
             mSubscriptionId = subscriptionId;
         }
 
-        public AccountSelectedListener(Context context, List<AccountWithDataSet> accountList,
+        public AccountSelectedListener(Activity activity, List<AccountWithDataSet> accountList,
                 int resId) {
             // Subscription id is only needed for importing from SIM card. We can safely ignore
             // its value for SD card importing.
-            this(context, accountList, resId, /* subscriptionId = */ -1);
+            this(activity, accountList, resId, /* subscriptionId = */ -1);
         }
 
         public void onClick(DialogInterface dialog, int which) {
             dialog.dismiss();
-            doImport(mContext, mResId, mAccountList.get(which), mSubscriptionId);
+            doImport(mActivity, mResId, mAccountList.get(which), mSubscriptionId);
         }
     }
 
-    public static Dialog getSelectAccountDialog(Context context, int resId) {
-        return getSelectAccountDialog(context, resId, null, null);
+    public static Dialog getSelectAccountDialog(Activity activity, int resId) {
+        return getSelectAccountDialog(activity, resId, null, null);
     }
 
-    public static Dialog getSelectAccountDialog(Context context, int resId,
+    public static Dialog getSelectAccountDialog(Activity activity, int resId,
             DialogInterface.OnClickListener onClickListener) {
-        return getSelectAccountDialog(context, resId, onClickListener, null);
+        return getSelectAccountDialog(activity, resId, onClickListener, null);
     }
 
     /**
      * When OnClickListener or OnCancelListener is null, uses a default listener.
      * The default OnCancelListener just closes itself with {@link Dialog#dismiss()}.
      */
-    public static Dialog getSelectAccountDialog(Context context, int resId,
+    public static Dialog getSelectAccountDialog(Activity activity, int resId,
             DialogInterface.OnClickListener onClickListener,
             DialogInterface.OnCancelListener onCancelListener) {
-        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
+        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(activity);
         final List<AccountWithDataSet> writableAccountList = accountTypes.getAccounts(true);
 
         Log.i(LOG_TAG, "The number of available accounts: " + writableAccountList.size());
@@ -108,12 +109,12 @@
 
         // Wrap our context to inflate list items using correct theme
         final Context dialogContext = new ContextThemeWrapper(
-                context, android.R.style.Theme_Light);
+                activity, android.R.style.Theme_Light);
         final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         final ArrayAdapter<AccountWithDataSet> accountAdapter =
             new ArrayAdapter<AccountWithDataSet>(
-                    context, R.layout.account_selector_list_item_condensed, writableAccountList) {
+                    activity, R.layout.account_selector_list_item_condensed, writableAccountList) {
             @Override
             public View getView(int position, View convertView, ViewGroup parent) {
                 if (convertView == null) {
@@ -141,7 +142,7 @@
 
         if (onClickListener == null) {
             AccountSelectedListener accountSelectedListener =
-                new AccountSelectedListener(context, writableAccountList, resId);
+                new AccountSelectedListener(activity, writableAccountList, resId);
             onClickListener = accountSelectedListener;
         }
         if (onCancelListener == null) {
@@ -151,8 +152,8 @@
                 }
             };
         }
-        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
-        final TextView title = (TextView) View.inflate(context, R.layout.dialog_title, null);
+        final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        final TextView title = (TextView) View.inflate(activity, R.layout.dialog_title, null);
         title.setText(R.string.dialog_new_contact_account);
         builder.setCustomTitle(title);
         builder.setSingleChoiceItems(accountAdapter, 0, onClickListener);
@@ -161,12 +162,12 @@
         return result;
     }
 
-    public static void doImport(Context context, int resId, AccountWithDataSet account,
+    public static void doImport(Activity activity, int resId, AccountWithDataSet account,
             int subscriptionId) {
         if (resId == R.string.import_from_sim) {
-            doImportFromSim(context, account, subscriptionId);
+            doImportFromSim(activity, account, subscriptionId);
         } else if (resId == R.string.import_from_vcf_file) {
-            doImportFromVcfFile(context, account);
+            doImportFromVcfFile(activity, account);
         }
     }
 
@@ -184,8 +185,8 @@
         context.startActivity(importIntent);
     }
 
-    public static void doImportFromVcfFile(Context context, AccountWithDataSet account) {
-        Intent importIntent = new Intent(context, ImportVCardActivity.class);
+    public static void doImportFromVcfFile(Activity activity, AccountWithDataSet account) {
+        Intent importIntent = new Intent(activity, ImportVCardActivity.class);
         if (account != null) {
             importIntent.putExtra("account_name", account.name);
             importIntent.putExtra("account_type", account.type);
@@ -198,6 +199,6 @@
         }
         mVCardShare = false;
         mPath = null;
-        context.startActivity(importIntent);
+        activity.startActivityForResult(importIntent, 0);
     }
 }
diff --git a/src/com/android/contacts/common/vcard/ImportVCardActivity.java b/src/com/android/contacts/common/vcard/ImportVCardActivity.java
index 93ab944..d7b64b4 100644
--- a/src/com/android/contacts/common/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/common/vcard/ImportVCardActivity.java
@@ -75,7 +75,7 @@
  * any Dialog in the instance. So this code is careless about the management around managed
  * dialogs stuffs (like how onCreateDialog() is used).
  */
-public class ImportVCardActivity extends Activity {
+public class ImportVCardActivity extends Activity implements ImportVCardDialogFragment.Listener {
     private static final String LOG_TAG = "VCardImport";
 
     private static final int SELECT_ACCOUNT = 0;
@@ -546,8 +546,8 @@
         // Reading uris from non-storage needs the permission granted from the source intent,
         // instead of permissions from RequestImportVCardPermissionActivity. So skipping requesting
         // permissions from RequestImportVCardPermissionActivity for uris from non-storage source.
-        if (isStorageUri(sourceUri)
-                && RequestImportVCardPermissionsActivity.startPermissionActivity(this)) {
+        if (isStorageUri(sourceUri) && RequestImportVCardPermissionsActivity
+                .startPermissionActivity(this, isCallerSelf(this))) {
             return;
         }
 
@@ -571,7 +571,8 @@
         }
 
         // Always request required permission for contacts before importing the vcard.
-        if (RequestImportVCardPermissionsActivity.startPermissionActivity(this)) {
+        if (RequestImportVCardPermissionsActivity.startPermissionActivity(this,
+                isCallerSelf(this))) {
             return;
         }
 
@@ -603,10 +604,44 @@
             }
         }
 
+        if (isCallerSelf(this)) {
+            startImport(sourceUri, sourceDisplayName);
+        } else {
+            ImportVCardDialogFragment.show(this, sourceUri, sourceDisplayName);
+        }
+    }
+
+    private static boolean isCallerSelf(Activity activity) {
+        // {@link Activity#getCallingActivity()} is a safer alternative to
+        // {@link Activity#getCallingPackage()} that works around a
+        // framework bug where getCallingPackage() can sometimes return null even when the
+        // current activity *was* in fact launched via a startActivityForResult() call.
+        //
+        // (The bug happens if the task stack needs to be re-created by the framework after
+        // having been killed due to memory pressure or by the "Don't keep activities"
+        // developer option; see bug 7494866 for the full details.)
+        //
+        // Turns out that {@link Activity#getCallingActivity()} *does* return correct info
+        // even in the case where getCallingPackage() is broken, so the workaround is simply
+        // to get the package name from getCallingActivity().getPackageName() instead.
+        final ComponentName callingActivity = activity.getCallingActivity();
+        if (callingActivity == null) return false;
+        final String packageName = callingActivity.getPackageName();
+        if (packageName == null) return false;
+        return packageName.equals(activity.getApplicationContext().getPackageName());
+    }
+
+    @Override
+    public void onImportVCardConfirmed(Uri sourceUri, String sourceDisplayName) {
         startImport(sourceUri, sourceDisplayName);
     }
 
     @Override
+    public void onImportVCardDenied() {
+        finish();
+    }
+
+    @Override
     public void onActivityResult(int requestCode, int resultCode, Intent intent) {
         if (requestCode == SELECT_ACCOUNT) {
             if (resultCode == Activity.RESULT_OK) {
diff --git a/src/com/android/contacts/common/vcard/ImportVCardDialogFragment.java b/src/com/android/contacts/common/vcard/ImportVCardDialogFragment.java
new file mode 100644
index 0000000..b1f1c3b
--- /dev/null
+++ b/src/com/android/contacts/common/vcard/ImportVCardDialogFragment.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.common.vcard;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.contacts.R;
+
+/** Asks for confirmation before importing contacts from a vcard. */
+public class ImportVCardDialogFragment extends DialogFragment {
+
+    static final String TAG = "importVCardDialog";
+
+    private static final String ARG_SOURCE_URI = "sourceUri";
+    private static final String ARG_SOURCE_DISPLAY_NAME = "sourceDisplayName";
+
+    /** Callbacks for hosts of the {@link ImportVCardDialogFragment}. */
+    public interface Listener {
+
+        /** Invoked after the user has confirmed that contacts should be imported. */
+        void onImportVCardConfirmed(Uri sourceUri, String sourceDisplayName);
+
+        /** Invoked after the user has rejected importing contacts. */
+        void onImportVCardDenied();
+    }
+
+    /** Displays the dialog asking for confirmation before importing contacts. */
+    public static void show(Activity activity, Uri sourceUri,
+            String sourceDisplayName) {
+        if (!(activity instanceof Listener)) {
+            throw new IllegalArgumentException(
+                    "Activity must implement " + Listener.class.getName());
+        }
+
+        final Bundle args = new Bundle();
+        args.putParcelable(ARG_SOURCE_URI, sourceUri);
+        args.putString(ARG_SOURCE_DISPLAY_NAME, sourceDisplayName);
+
+        final ImportVCardDialogFragment dialog = new ImportVCardDialogFragment();
+        dialog.setArguments(args);
+        dialog.show(activity.getFragmentManager(), TAG);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final Uri sourceUri = getArguments().getParcelable(ARG_SOURCE_URI);
+        final String sourceDisplayName = getArguments().getString(ARG_SOURCE_DISPLAY_NAME);
+
+        return new AlertDialog.Builder(getActivity())
+                .setIconAttribute(android.R.attr.alertDialogIcon)
+                .setMessage(R.string.import_from_vcf_file_confirmation_message)
+                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        final Listener listener = (Listener) getActivity();
+                        if (listener != null) {
+                            listener.onImportVCardConfirmed(sourceUri, sourceDisplayName);
+                        }
+                    }
+                })
+                .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        final Listener listener = (Listener) getActivity();
+                        if (listener != null) {
+                            listener.onImportVCardDenied();
+                        }
+                    }
+                })
+                .create();
+    }
+}
diff --git a/src/com/android/contacts/editor/PickRawContactDialogFragment.java b/src/com/android/contacts/editor/PickRawContactDialogFragment.java
index aa65b01..6b0051e 100644
--- a/src/com/android/contacts/editor/PickRawContactDialogFragment.java
+++ b/src/com/android/contacts/editor/PickRawContactDialogFragment.java
@@ -192,6 +192,11 @@
         }
     }
 
+    @Override
+    public Context getContext() {
+        return getActivity();
+    }
+
     public void setCursor(Cursor cursor) {
         if (mAdapter != null) {
             mAdapter.swapCursor(cursor);
diff --git a/src/com/android/contacts/group/GroupMembersFragment.java b/src/com/android/contacts/group/GroupMembersFragment.java
index 2f61cdd..4f8f6a8 100644
--- a/src/com/android/contacts/group/GroupMembersFragment.java
+++ b/src/com/android/contacts/group/GroupMembersFragment.java
@@ -64,6 +64,7 @@
 import com.android.contacts.list.ContactsRequest;
 import com.android.contacts.list.MultiSelectContactsListFragment;
 import com.android.contacts.list.UiIntentActions;
+import com.android.contactsbind.FeedbackHelper;
 import com.android.contactsbind.experiments.Flags;
 import com.google.common.primitives.Longs;
 
@@ -310,6 +311,7 @@
                 ContactsContract.Data.CONTACT_ID,
                 ContactsContract.CommonDataKinds.Email._ID,
                 ContactsContract.Data.IS_SUPER_PRIMARY,
+                ContactsContract.Data.TIMES_USED,
                 ContactsContract.Data.DATA1
         };
 
@@ -317,13 +319,47 @@
                 ContactsContract.Data.CONTACT_ID,
                 ContactsContract.CommonDataKinds.Phone._ID,
                 ContactsContract.Data.IS_SUPER_PRIMARY,
+                ContactsContract.Data.TIMES_USED,
                 ContactsContract.Data.DATA1
         };
 
         public static final int CONTACT_ID = 0;
         public static final int ITEM_ID = 1;
         public static final int PRIMARY = 2;
-        public static final int DATA1 = 3;
+        public static final int TIMES_USED = 3;
+        public static final int DATA1 = 4;
+    }
+
+    /**
+     * Helper class for managing data related to contacts and emails/phone numbers.
+     */
+    private class ContactDataHelperClass {
+
+        private List<String> items = new ArrayList<>();
+        private String mostUsedItemId = null;
+        private int mostUsedTimes;
+        private String primaryItemId = null;
+
+        public void addItem(String item, int timesUsed, boolean primaryFlag) {
+            if (mostUsedItemId == null || timesUsed > mostUsedTimes) {
+                mostUsedItemId = item;
+                mostUsedTimes = timesUsed;
+            }
+            if (primaryFlag) {
+                primaryItemId = item;
+            }
+            items.add(item);
+        }
+
+        public boolean hasDefaultItem() {
+            return primaryItemId != null || items.size() == 1;
+        }
+
+        public String getDefaultSelectionItemId() {
+            return primaryItemId != null
+                    ? primaryItemId
+                    : mostUsedItemId;
+        }
     }
 
     private List<String> getSendToDataForIds(long[] ids, String scheme) {
@@ -365,10 +401,8 @@
         if(ids == null || ids.length == 0) return;
 
         // Get emails or phone numbers
-        // encounteredIds <contact_id, <item_id, is_super_primary>>
-        final Map<String, Map<String, Boolean>> encounteredIds = new HashMap<>();
-        // primaryItems <contact_id, has_super_primary>
-        final Map<String, Boolean> primaryItems = new HashMap<>();
+        // contactMap <contact_id, contact_data>
+        final Map<String, ContactDataHelperClass> contactMap = new HashMap<>();
         // itemList <item_data>
         final List<String> itemList = new ArrayList<>();
         final String sIds = GroupUtil.convertArrayToString(ids);
@@ -393,20 +427,18 @@
                 final String contactId = cursor.getString(Query.CONTACT_ID);
                 final String itemId = cursor.getString(Query.ITEM_ID);
                 final boolean isPrimary = cursor.getInt(Query.PRIMARY) != 0;
+                final int timesUsed = cursor.getInt(Query.TIMES_USED);
                 final String data = cursor.getString(Query.DATA1);
 
-                if (!encounteredIds.containsKey(contactId)) {
-                    encounteredIds.put(contactId, new HashMap<String, Boolean>());
-                }
-                final Boolean prevHasSuperPrimary = primaryItems.get(contactId);
-                final boolean hasPrimary = prevHasSuperPrimary == null
-                        ? isPrimary
-                        : prevHasSuperPrimary || isPrimary;
-                primaryItems.put(contactId, hasPrimary);
-
                 if (!TextUtils.isEmpty(data)) {
-                    final Map<String, Boolean> itemMap = encounteredIds.get(contactId);
-                    itemMap.put(itemId, isPrimary);
+                    final ContactDataHelperClass contact;
+                    if (!contactMap.containsKey(contactId)) {
+                        contact = new ContactDataHelperClass();
+                        contactMap.put(contactId, contact);
+                    } else {
+                        contact = contactMap.get(contactId);
+                    }
+                    contact.addItem(itemId, timesUsed, isPrimary);
                     itemList.add(data);
                 }
             }
@@ -414,18 +446,15 @@
             cursor.close();
         }
 
-        // Start picker if a contact has multiple items with no superPrimary
-        for (Map.Entry<String, Map<String, Boolean>> i : encounteredIds.entrySet()) {
-            boolean hasSuperPrimary = primaryItems.get(i.getKey());
-            if (i.getValue().size() > 1 && !hasSuperPrimary) {
+        // Start picker if a contact does not have a default
+        for (ContactDataHelperClass i : contactMap.values()) {
+            if (!i.hasDefaultItem()) {
                 // Build list of default selected item ids
                 final List<Long> defaultSelection = new ArrayList<>();
-                for (Map.Entry<String, Map<String, Boolean>> j : encounteredIds.entrySet()) {
-                    for (Map.Entry<String, Boolean> k : j.getValue().entrySet()) {
-                        final String itemId = k.getKey();
-                        if (j.getValue().size() == 1 || k.getValue()) {
-                            defaultSelection.add(Long.parseLong(itemId));
-                        }
+                for (ContactDataHelperClass j : contactMap.values()) {
+                    final String selectionItemId = j.getDefaultSelectionItemId();
+                    if (selectionItemId != null) {
+                        defaultSelection.add(Long.parseLong(selectionItemId));
                     }
                 }
                 final long[] defaultSelectionArray = Longs.toArray(defaultSelection);
@@ -434,7 +463,7 @@
             }
         }
 
-        if (itemList.size() == 0 || encounteredIds.size() < ids.length) {
+        if (itemList.size() == 0 || contactMap.size() < ids.length) {
             Toast.makeText(getContext(), ContactsUtils.SCHEME_MAILTO.equals(sendScheme)
                             ? getString(R.string.groupSomeContactsNoEmailsToast)
                             : getString(R.string.groupSomeContactsNoPhonesToast),
@@ -882,9 +911,7 @@
     }
 
     public void updateExistingGroupFragment(Uri newGroupUri, String action) {
-        if (!GroupUtil.ACTION_SWITCH_GROUP.equals(action)) {
-            toast(getToastMessageForSaveAction(action));
-        }
+        toastForSaveAction(action);
 
         if (isEditMode() && getGroupCount() == 1) {
             // If we're deleting the last group member, exit edit mode
@@ -897,19 +924,30 @@
         }
     }
 
-    private static int getToastMessageForSaveAction(String action) {
+    public void toastForSaveAction(String action) {
+        int id = -1;
         switch(action) {
             case GroupUtil.ACTION_UPDATE_GROUP:
-                return R.string.groupUpdatedToast;
-            case GroupUtil.ACTION_ADD_TO_GROUP:
-                return R.string.groupMembersAddedToast;
+                id = R.string.groupUpdatedToast;
+                break;
             case GroupUtil.ACTION_REMOVE_FROM_GROUP:
-                return R.string.groupMembersRemovedToast;
+                id = R.string.groupMembersRemovedToast;
+                break;
             case GroupUtil.ACTION_CREATE_GROUP:
-                return R.string.groupCreatedToast;
+                id = R.string.groupCreatedToast;
+                break;
+            case GroupUtil.ACTION_ADD_TO_GROUP:
+                id = R.string.groupMembersAddedToast;
+                break;
+            case GroupUtil.ACTION_SWITCH_GROUP:
+                // No toast associated with this action.
+                break;
             default:
-                throw new IllegalArgumentException("Unhandled contact save action " + action);
+                FeedbackHelper.sendFeedback(getContext(), TAG,
+                        "toastForSaveAction passed unknown action: " + action,
+                        new IllegalArgumentException("Unhandled contact save action " + action));
         }
+        toast(id);
     }
 
     private void toast(int resId) {
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 3597607..52c66d5 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -2275,7 +2275,7 @@
                     return;
                 }
 
-                if (!mIsRecreatedInstance && !mShortcutUsageReported) {
+                if (!mIsRecreatedInstance && !mShortcutUsageReported && data != null) {
                     mShortcutUsageReported = true;
                     DynamicShortcuts.reportShortcutUsed(QuickContactActivity.this,
                             data.getLookupKey());
diff --git a/src/com/android/contacts/util/SharedPreferenceUtil.java b/src/com/android/contacts/util/SharedPreferenceUtil.java
index 80e4825..cee54b9 100644
--- a/src/com/android/contacts/util/SharedPreferenceUtil.java
+++ b/src/com/android/contacts/util/SharedPreferenceUtil.java
@@ -18,8 +18,6 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.telephony.SubscriptionInfo;
-import android.telephony.TelephonyManager;
 
 import com.android.contacts.common.model.SimCard;
 
@@ -53,6 +51,12 @@
     private static final String PREFERENCE_KEY_DISMISSED_SIM_CARDS =
             "dismissedSimCards";
 
+    private static final String PREFERENCE_KEY_RESTORED_DEVICES =
+            "restoredDevices";
+
+    private static final String PREFERENCE_KEY_DISMISSED_DEVICES =
+            "dismissedDevices";
+
     public static boolean getHamburgerPromoDisplayedBefore(Context context) {
         return getSharedPreferences(context)
                 .getBoolean(PREFERENCE_KEY_HAMBURGER_PROMO_DISPLAYED_BEFORE, false);
@@ -191,6 +195,40 @@
                 .getStringSet(PREFERENCE_KEY_DISMISSED_SIM_CARDS, Collections.<String>emptySet());
     }
 
+    public static Set<String> getRestoredDevices(Context context) {
+        return getSharedPreferences(context)
+                .getStringSet(PREFERENCE_KEY_RESTORED_DEVICES, Collections.<String>emptySet());
+    }
+
+    public static Set<String> getDismissedDevices(Context context) {
+        return getSharedPreferences(context)
+                .getStringSet(PREFERENCE_KEY_DISMISSED_DEVICES, Collections.<String>emptySet());
+    }
+
+    public static void addRestoredDevice(Context context, String deviceId) {
+        final Set<String> restoredDevices = new HashSet<>(getRestoredDevices(context));
+        restoredDevices.add(deviceId);
+        getSharedPreferences(context).edit()
+                .putStringSet(PREFERENCE_KEY_RESTORED_DEVICES, restoredDevices)
+                .apply();
+    }
+
+    public static void addDismissedDevice(Context context, String deviceId) {
+        final Set<String> dismissedDevices = new HashSet<>(getDismissedDevices(context));
+        dismissedDevices.add(deviceId);
+        getSharedPreferences(context).edit()
+                .putStringSet(PREFERENCE_KEY_DISMISSED_DEVICES, dismissedDevices)
+                .commit();
+    }
+
+    public static void removeDismissedDevice(Context context, String deviceId) {
+        final Set<String> dismissedDevices = new HashSet<>(getDismissedDevices(context));
+        dismissedDevices.remove(deviceId);
+        getSharedPreferences(context).edit()
+                .putStringSet(PREFERENCE_KEY_DISMISSED_DEVICES, dismissedDevices)
+                .commit();
+    }
+
     public static void clear(Context context) {
         getSharedPreferences(context).edit().clear().commit();
     }