Add better support for importing from multiple SIMs

For I102f4f14ae976e550f65c83ee695d7cdc241e4e1

Test
See I102f4f14ae976e550f65c83ee695d7cdc241e4e1

Bug 31781331
Change-Id: I849e441d1ab42bff135b300b34595eb8ed468005
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 1b2c22d..853e676 100755
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -45,6 +45,8 @@
 import android.provider.ContactsContract.Profile;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.RawContactsEntity;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.content.LocalBroadcastManager;
 import android.support.v4.os.ResultReceiver;
 import android.text.TextUtils;
@@ -150,6 +152,7 @@
 
     public static final String BROADCAST_GROUP_DELETED = "groupDeleted";
     public static final String BROADCAST_SIM_IMPORT_COMPLETE = "simImportComplete";
+    public static final String EXTRA_CALLBACK_DATA = "extraCallbackData";
 
     public static final String EXTRA_RESULT_CODE = "resultCode";
     public static final String EXTRA_RESULT_COUNT = "count";
@@ -208,7 +211,7 @@
     public void onCreate() {
         super.onCreate();
         mGroupsDao = new GroupsDaoImpl(this);
-        mSimContactDao = new SimContactDao(this);
+        mSimContactDao = SimContactDao.create(this);
     }
 
     public static void registerListener(Listener listener) {
@@ -1685,12 +1688,14 @@
         operations.add(builder.build());
     }
 
-    public static Intent createImportFromSimIntent(Context context,
-            ArrayList<SimContact> contacts, AccountWithDataSet targetAccount) {
+    public static Intent createImportFromSimIntent(@NonNull Context context,
+            @NonNull ArrayList<SimContact> contacts, @NonNull AccountWithDataSet targetAccount,
+            @Nullable  Bundle callbackData) {
         return new Intent(context, ContactSaveService.class)
                 .setAction(ACTION_IMPORT_FROM_SIM)
                 .putExtra(EXTRA_SIM_CONTACTS, contacts)
-                .putExtra(EXTRA_ACCOUNT, targetAccount);
+                .putExtra(EXTRA_ACCOUNT, targetAccount)
+                .putExtra(EXTRA_CALLBACK_DATA, callbackData);
     }
 
     private void importFromSim(Intent intent) {
@@ -1704,7 +1709,8 @@
             // notify success
             LocalBroadcastManager.getInstance(this).sendBroadcast(result
                     .putExtra(EXTRA_RESULT_COUNT, contacts.size())
-                    .putExtra(EXTRA_RESULT_CODE, RESULT_SUCCESS));
+                    .putExtra(EXTRA_RESULT_CODE, RESULT_SUCCESS)
+                    .putExtra(EXTRA_CALLBACK_DATA, intent.getBundleExtra(EXTRA_CALLBACK_DATA)));
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, "importFromSim completed successfully");
             }
diff --git a/src/com/android/contacts/SimImportFragment.java b/src/com/android/contacts/SimImportFragment.java
index 7ce00d2..ed043bf 100644
--- a/src/com/android/contacts/SimImportFragment.java
+++ b/src/com/android/contacts/SimImportFragment.java
@@ -40,6 +40,7 @@
 import com.android.contacts.common.list.ContactListItemView;
 import com.android.contacts.common.list.MultiSelectEntryContactListAdapter;
 import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.SimCard;
 import com.android.contacts.common.model.SimContact;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.preference.ContactsPreferences;
@@ -59,7 +60,8 @@
 
     private static final String KEY_SELECTED_IDS = "selectedIds";
     private static final String ARG_SUBSCRIPTION_ID = "subscriptionId";
-    public static final int NO_SUBSCRIPTION_ID = -1;
+
+    public static final String CALLBACK_KEY_SUBSCRIPTION_ID = "simSubscriptionId";
 
     private ContactsPreferences mPreferences;
     private AccountTypeManager mAccountTypeManager;
@@ -88,8 +90,8 @@
         mAdapter.setHasHeader(0, false);
 
         final Bundle args = getArguments();
-        mSubscriptionId = args == null ? NO_SUBSCRIPTION_ID : args.getInt(ARG_SUBSCRIPTION_ID,
-                NO_SUBSCRIPTION_ID);
+        mSubscriptionId = args == null ? SimCard.NO_SUBSCRIPTION_ID :
+                args.getInt(ARG_SUBSCRIPTION_ID, SimCard.NO_SUBSCRIPTION_ID);
 
         if (savedInstanceState == null) return;
         mSelectedContacts = savedInstanceState.getLongArray(KEY_SELECTED_IDS);
@@ -198,9 +200,11 @@
     }
 
     private void importCurrentSelections() {
+        final Bundle callbackData = new Bundle();
+        callbackData.putInt(CALLBACK_KEY_SUBSCRIPTION_ID, mSubscriptionId);
         ContactSaveService.startService(getContext(), ContactSaveService
                 .createImportFromSimIntent(getContext(), mAdapter.getSelectedContacts(),
-                mAccountHeaderPresenter.getCurrentAccount()));
+                mAccountHeaderPresenter.getCurrentAccount(), callbackData));
     }
 
     @Override
@@ -321,7 +325,7 @@
 
         public SimContactLoader(Context context, int subscriptionId) {
             super(context);
-            mDao = new SimContactDao(context);
+            mDao = SimContactDao.create(context);
             mSubscriptionId = subscriptionId;
         }
 
@@ -342,7 +346,7 @@
 
         @Override
         public ArrayList<SimContact> loadInBackground() {
-            if (mSubscriptionId != NO_SUBSCRIPTION_ID) {
+            if (mSubscriptionId != SimCard.NO_SUBSCRIPTION_ID) {
                 return mDao.loadSimContacts(mSubscriptionId);
             } else {
                 return mDao.loadSimContacts();
diff --git a/src/com/android/contacts/common/database/SimContactDao.java b/src/com/android/contacts/common/database/SimContactDao.java
index 8d7855f..d55a70f 100644
--- a/src/com/android/contacts/common/database/SimContactDao.java
+++ b/src/com/android/contacts/common/database/SimContactDao.java
@@ -15,6 +15,7 @@
  */
 package com.android.contacts.common.database;
 
+import android.annotation.TargetApi;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
 import android.content.ContentResolver;
@@ -28,25 +29,25 @@
 import android.os.RemoteException;
 import android.provider.BaseColumns;
 import android.provider.ContactsContract;
+import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.util.ArraySet;
-import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.contacts.common.Experiments;
+import com.android.contacts.common.compat.CompatUtils;
+import com.android.contacts.common.model.SimCard;
 import com.android.contacts.common.model.SimContact;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.util.PermissionsUtil;
 import com.android.contacts.util.SharedPreferenceUtil;
 import com.android.contactsbind.experiments.Flags;
-import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Provides data access methods for loading contacts from a SIM card and and migrating these
@@ -55,6 +56,10 @@
 public class SimContactDao {
     private static final String TAG = "SimContactDao";
 
+    // Set to true for manual testing on an emulator or phone without a SIM card
+    // DO NOT SUBMIT if set to true
+    private static final boolean USE_FAKE_INSTANCE = false;
+
     @VisibleForTesting
     public static final Uri ICC_CONTENT_URI = Uri.parse("content://icc/adn");
 
@@ -76,64 +81,49 @@
     public void warmupSimQueryIfNeeded() {
         // Not needed if we don't have an Assistant section
         if (!Flags.getInstance().getBoolean(Experiments.ASSISTANT) ||
-                !shouldLoad()) return;
+                !canReadSimContacts()) return;
 
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                // We don't actually have to do any caching ourselves. Some other layer must do
-                // caching of the data (OS or framework) because subsequent queries are very fast.
-                final Cursor cursor = mResolver.query(ICC_CONTENT_URI, null, null, null, null);
-                if (cursor != null) {
-                    cursor.close();
+                for (SimCard card : getSimCards()) {
+                    // We don't actually have to do any caching ourselves. Some other layer must do
+                    // caching of the data (OS or framework) because subsequent queries are very
+                    // fast.
+                    card.loadContacts(SimContactDao.this);
                 }
                 return null;
             }
         }.execute();
     }
 
-    public boolean shouldLoad() {
-        final Set<String> simIds = hasTelephony() && hasPermissions() ? getSimCardIds() :
-                Collections.<String>emptySet();
-
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "shouldLoad: hasTelephony? " + hasTelephony() +
-                    " hasPermissions? " + hasPermissions() +
-                    " SIM absent? " + (getSimState() != TelephonyManager.SIM_STATE_ABSENT) +
-                    " SIM ids=" + simIds +
-                    " imported=" + SharedPreferenceUtil.getImportedSims(mContext));
-        }
+    public boolean canReadSimContacts() {
         return hasTelephony() && hasPermissions() &&
-                getSimState() != TelephonyManager.SIM_STATE_ABSENT &&
-                !Sets.difference(simIds, SharedPreferenceUtil.getImportedSims(mContext))
-                        .isEmpty();
+                mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT;
     }
 
-    public Set<String> getSimCardIds() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
-            final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext);
-            final List<SubscriptionInfo> subscriptions = subscriptionManager
-                    .getActiveSubscriptionInfoList();
-            if (subscriptions == null) {
-                return Collections.emptySet();
-            }
-            final ArraySet<String> result = new ArraySet<>(
-                    subscriptionManager.getActiveSubscriptionInfoCount());
-
-            for (SubscriptionInfo info : subscriptions) {
-                result.add(info.getIccId());
-            }
-            return result;
+    public List<SimCard> getSimCards() {
+        if (!canReadSimContacts()) {
+            return Collections.emptyList();
         }
-        return Collections.singleton(getSimSerialNumber());
+        final List<SimCard> sims = CompatUtils.isMSIMCompatible() ?
+                getSimCardsFromSubscriptions() :
+                Collections.singletonList(SimCard.create(mTelephonyManager));
+        return SharedPreferenceUtil.restoreSimStates(mContext, sims);
     }
 
-    public int getSimState() {
-        return mTelephonyManager.getSimState();
-    }
-
-    public String getSimSerialNumber() {
-        return mTelephonyManager.getSimSerialNumber();
+    @NonNull
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
+    private List<SimCard> getSimCardsFromSubscriptions() {
+        final SubscriptionManager subscriptionManager = (SubscriptionManager)
+                mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        final List<SubscriptionInfo> subscriptions = subscriptionManager
+                .getActiveSubscriptionInfoList();
+        final ArrayList<SimCard> result = new ArrayList<>();
+        for (SubscriptionInfo subscriptionInfo : subscriptions) {
+            result.add(SimCard.create(subscriptionInfo));
+        }
+        return result;
     }
 
     public ArrayList<SimContact> loadSimContacts(int subscriptionId) {
@@ -147,6 +137,10 @@
         return loadFrom(ICC_CONTENT_URI);
     }
 
+    public Context getContext() {
+        return mContext;
+    }
+
     private ArrayList<SimContact> loadFrom(Uri uri) {
         final Cursor cursor = mResolver.query(uri, null, null, null, null);
 
@@ -185,6 +179,31 @@
         return mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
     }
 
+    public void persistSimState(SimCard sim) {
+        SharedPreferenceUtil.persistSimStates(mContext, Collections.singletonList(sim));
+    }
+
+    public void persistSimStates(List<SimCard> simCards) {
+        SharedPreferenceUtil.persistSimStates(mContext, simCards);
+    }
+
+    public SimCard getFirstSimCard() {
+        return getSimBySubscriptionId(SimCard.NO_SUBSCRIPTION_ID);
+    }
+
+    public SimCard getSimBySubscriptionId(int subscriptionId) {
+        final List<SimCard> sims = getSimCards();
+        if (subscriptionId == SimCard.NO_SUBSCRIPTION_ID && !sims.isEmpty()) {
+            return sims.get(0);
+        }
+        for (SimCard sim : getSimCards()) {
+            if (sim.getSubscriptionId() == subscriptionId) {
+                return sim;
+            }
+        }
+        return null;
+    }
+
     private ArrayList<ContentProviderOperation> createImportOperations(List<SimContact> contacts,
             AccountWithDataSet targetAccount) {
         final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
@@ -198,15 +217,6 @@
         return emails != null ? emails.split(",") : null;
     }
 
-    public void persistImportSuccess() {
-        // TODO: either need to have an assistant card per SIM card or show contacts from all
-        // SIMs in the import view.
-        final Set<String> simIds = getSimCardIds();
-        for (String id : simIds) {
-            SharedPreferenceUtil.addImportedSim(mContext, id);
-        }
-    }
-
     private boolean hasTelephony() {
         return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
     }
@@ -215,4 +225,68 @@
         return PermissionsUtil.hasContactsPermissions(mContext) &&
                 PermissionsUtil.hasPhonePermissions(mContext);
     }
+
+    public static SimContactDao create(Context context) {
+        if (USE_FAKE_INSTANCE) {
+            return new DebugImpl(context)
+                    .addSimCard(new SimCard("fake-sim-id1", 1, "Fake Carrier", "Card 1",
+                            "15095550101", "us").withContacts(
+                            new SimContact(1, "Sim One", "15095550111", null),
+                            new SimContact(2, "Sim Two", "15095550112", null),
+                            new SimContact(3, "Sim Three", "15095550113", null),
+                            new SimContact(4, "Sim Four", "15095550114", null)
+                    ))
+                    .addSimCard(new SimCard("fake-sim-id2", 1, "Carrier Two", "Card 2",
+                            "15095550102", "us").withContacts(
+                            new SimContact(1, "John Sim", "15095550121", null),
+                            new SimContact(2, "Bob Sim", "15095550122", null),
+                            new SimContact(3, "Mary Sim", "15095550123", null),
+                            new SimContact(4, "Alice Sim", "15095550124", null)
+                    ));
+        }
+
+        return new SimContactDao(context);
+    }
+
+    // TODO remove this class and the USE_FAKE_INSTANCE flag once this code is not under
+    // active development or anytime after 3/1/2017
+    private static class DebugImpl extends SimContactDao {
+
+        private List<SimCard> mSimCards = new ArrayList<>();
+        private SparseArray<SimCard> mCardsBySubscription = new SparseArray<>();
+
+        public DebugImpl(Context context) {
+            super(context);
+        }
+
+        public DebugImpl addSimCard(SimCard sim) {
+            mSimCards.add(sim);
+            mCardsBySubscription.put(sim.getSubscriptionId(), sim);
+            return this;
+        }
+
+        @Override
+        public void warmupSimQueryIfNeeded() {
+        }
+
+        @Override
+        public List<SimCard> getSimCards() {
+            return SharedPreferenceUtil.restoreSimStates(getContext(), mSimCards);
+        }
+
+        @Override
+        public ArrayList<SimContact> loadSimContacts() {
+            return new ArrayList<>(mSimCards.get(0).getContacts());
+        }
+
+        @Override
+        public ArrayList<SimContact> loadSimContacts(int subscriptionId) {
+            return new ArrayList<>(mCardsBySubscription.get(subscriptionId).getContacts());
+        }
+
+        @Override
+        public boolean canReadSimContacts() {
+            return true;
+        }
+    }
 }
diff --git a/src/com/android/contacts/common/model/SimCard.java b/src/com/android/contacts/common/model/SimCard.java
new file mode 100644
index 0000000..ad91546
--- /dev/null
+++ b/src/com/android/contacts/common/model/SimCard.java
@@ -0,0 +1,198 @@
+/*
+ * 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.model;
+
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.contacts.common.database.SimContactDao;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Holds data for a SIM card in the device.
+ */
+public class SimCard {
+
+    private static final String TAG = "SimCard";
+
+    public static final int NO_SUBSCRIPTION_ID = -1;
+
+    // This state is created from the info we get from the system
+    private final String mSimId;
+    private final int mSubscriptionId;
+    private final CharSequence mCarrierName;
+    private final CharSequence mDisplayName;
+    private final String mPhoneNumber;
+    private final String mCountryCode;
+
+    // This is our own state that we associate with SIM cards. Currently these are only used
+    // in the GoogleContacts app.
+    // Note: these are logically immutable but are not final to reduce required constructor
+    // parameters
+    private boolean mDismissed = false;
+    private boolean mImported = false;
+
+    private List<SimContact> mContacts;
+
+    public SimCard(SimCard other) {
+        mSimId = other.mSimId;
+        mSubscriptionId = other.mSubscriptionId;
+        mCarrierName = other.mCarrierName;
+        mDisplayName = other.mDisplayName;
+        mPhoneNumber = other.mPhoneNumber;
+        mCountryCode = other.mCountryCode;
+        mDismissed = other.mDismissed;
+        mImported = other.mImported;
+        mContacts = other.mContacts;
+    }
+
+    public SimCard(String simId, int subscriptionId, CharSequence carrierName,
+            CharSequence displayName, String phoneNumber, String countryCode) {
+        mSimId = simId;
+        mSubscriptionId = subscriptionId;
+        mCarrierName = carrierName;
+        mDisplayName = displayName;
+        mPhoneNumber = phoneNumber;
+        mCountryCode = countryCode;
+    }
+
+    public SimCard(String simId, CharSequence carrierName,
+            CharSequence displayName, String phoneNumber, String countryCode) {
+        this(simId, NO_SUBSCRIPTION_ID, carrierName, displayName, phoneNumber, countryCode);
+    }
+
+    public String getSimId() {
+        return mSimId;
+    }
+
+    public int getSubscriptionId() {
+        return mSubscriptionId;
+    }
+
+    public boolean hasContacts() {
+        if (mContacts == null) {
+            throw new IllegalStateException("Contacts not loaded.");
+        }
+        return !mContacts.isEmpty();
+    }
+
+    public int getContactCount() {
+        if (mContacts == null) {
+            throw new IllegalStateException("Contacts not loaded.");
+        }
+        return mContacts.size();
+    }
+
+    public boolean isDismissed() {
+        return mDismissed;
+    }
+
+    public boolean isImported() {
+        return mImported;
+    }
+
+    public boolean isImportable() {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "isImportable: isDismissed? " + isDismissed() +
+                    " isImported? " + isImported() + " contacts=" + mContacts);
+        }
+        return !isDismissed() && !isImported() && hasContacts();
+    }
+
+    public List<SimContact> getContacts() {
+        if (mContacts == null) {
+            throw new IllegalStateException("Contacts not loaded.");
+        }
+        return mContacts;
+    }
+
+    public synchronized void loadContacts(SimContactDao dao) {
+        if (mSubscriptionId == NO_SUBSCRIPTION_ID) {
+            // Load from the default SIM card.
+            mContacts = dao.loadSimContacts();
+        } else {
+            mContacts = dao.loadSimContacts(mSubscriptionId);
+        }
+        if (mContacts == null) {
+            mContacts = Collections.emptyList();
+        }
+    }
+
+    public SimCard withImportAndDismissStates(boolean imported, boolean dismissed) {
+        SimCard copy = new SimCard(this);
+        copy.mImported = imported;
+        copy.mDismissed = dismissed;
+        return copy;
+    }
+
+    public SimCard withImportedState(boolean imported) {
+        return withImportAndDismissStates(imported, mDismissed);
+    }
+
+    public SimCard withDismissedState(boolean dismissed) {
+        return withImportAndDismissStates(dismissed, mImported);
+    }
+
+    public SimCard withContacts(SimContact... contacts) {
+        final SimCard copy = new SimCard(this);
+        copy.mContacts = Arrays.asList(contacts);
+        return copy;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        SimCard simCard = (SimCard) o;
+
+        return mSubscriptionId == simCard.mSubscriptionId && mDismissed == simCard.mDismissed &&
+                mImported == simCard.mImported && Objects.equals(mSimId, simCard.mSimId) &&
+                Objects.equals(mPhoneNumber, simCard.mPhoneNumber) &&
+                Objects.equals(mCountryCode, simCard.mCountryCode);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Objects.hash(mSimId, mPhoneNumber, mCountryCode);
+        result = 31 * result + mSubscriptionId;
+        result = 31 * result + (mDismissed ? 1 : 0);
+        result = 31 * result + (mImported ? 1 : 0);
+        return result;
+    }
+
+
+    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
+    public static SimCard create(SubscriptionInfo info) {
+        return new SimCard(info.getIccId(), info.getSubscriptionId(),
+                info.getCarrierName(), info.getDisplayName(), info.getNumber(),
+                info.getCountryIso());
+    }
+
+    public static SimCard create(TelephonyManager telephony) {
+        SimCard simCard = new SimCard(telephony.getSimSerialNumber(),
+                telephony.getNetworkOperatorName(), "SIM", telephony.getLine1Number(),
+                telephony.getNetworkCountryIso());
+        return simCard;
+    }
+}
diff --git a/src/com/android/contacts/util/SharedPreferenceUtil.java b/src/com/android/contacts/util/SharedPreferenceUtil.java
index 2f899e3..80e4825 100644
--- a/src/com/android/contacts/util/SharedPreferenceUtil.java
+++ b/src/com/android/contacts/util/SharedPreferenceUtil.java
@@ -21,8 +21,13 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
 
+import com.android.contacts.common.model.SimCard;
+
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 public class SharedPreferenceUtil {
@@ -45,6 +50,9 @@
     private static final String PREFERENCE_KEY_IMPORTED_SIM_CARDS =
             "importedSimCards";
 
+    private static final String PREFERENCE_KEY_DISMISSED_SIM_CARDS =
+            "dismissedSimCards";
+
     public static boolean getHamburgerPromoDisplayedBefore(Context context) {
         return getSharedPreferences(context)
                 .getBoolean(PREFERENCE_KEY_HAMBURGER_PROMO_DISPLAYED_BEFORE, false);
@@ -141,22 +149,49 @@
                 .putInt(buildSharedPrefsName(accountName), value + 1).apply();
     }
 
-    /**
-     * Persist an identifier for a SIM card which has been successfully imported.
-     *
-     * @param simId an identifier for the SIM card this should be one of
-     * {@link TelephonyManager#getSimSerialNumber()} or {@link SubscriptionInfo#getIccId()}
-     * depending on API level. The source of the value should be consistent on a particular device
-     */
-    public static void addImportedSim(Context context, String simId) {
-        final Set<String> current = new HashSet<>(getImportedSims(context));
-        current.add(simId);
+    public static void persistSimStates(Context context, Collection<SimCard> sims) {
+        final Set<String> imported = new HashSet<>(getImportedSims(context));
+        final Set<String> dismissed = new HashSet<>(getDismissedSims(context));
+        for (SimCard sim : sims) {
+            if (sim.isImported()) {
+                imported.add(sim.getSimId());
+            } else {
+                imported.remove(sim.getSimId());
+            }
+            if (sim.isDismissed()) {
+                dismissed.add(sim.getSimId());
+            } else {
+                dismissed.remove(sim.getSimId());
+            }
+        }
         getSharedPreferences(context).edit()
-                .putStringSet(PREFERENCE_KEY_IMPORTED_SIM_CARDS, current).apply();
+                .putStringSet(PREFERENCE_KEY_IMPORTED_SIM_CARDS, imported)
+                .putStringSet(PREFERENCE_KEY_DISMISSED_SIM_CARDS, dismissed)
+                .apply();
     }
 
-    public static Set<String> getImportedSims(Context context) {
+    public static List<SimCard> restoreSimStates(Context context, List<SimCard> sims) {
+        final Set<String> imported = getImportedSims(context);
+        final Set<String> dismissed = getDismissedSims(context);
+        List<SimCard> result = new ArrayList<>();
+        for (SimCard sim : sims) {
+            result.add(sim.withImportAndDismissStates(imported.contains(sim.getSimId()),
+                    dismissed.contains(sim.getSimId())));
+        }
+        return result;
+    }
+
+    private static Set<String> getImportedSims(Context context) {
         return getSharedPreferences(context)
                 .getStringSet(PREFERENCE_KEY_IMPORTED_SIM_CARDS, Collections.<String>emptySet());
     }
+
+    private static Set<String> getDismissedSims(Context context) {
+        return getSharedPreferences(context)
+                .getStringSet(PREFERENCE_KEY_DISMISSED_SIM_CARDS, Collections.<String>emptySet());
+    }
+
+    public static void clear(Context context) {
+        getSharedPreferences(context).edit().clear().commit();
+    }
 }
diff --git a/tests/src/com/android/contacts/tests/AdbHelpers.java b/tests/src/com/android/contacts/tests/AdbHelpers.java
index c7e2188..0024dd1 100644
--- a/tests/src/com/android/contacts/tests/AdbHelpers.java
+++ b/tests/src/com/android/contacts/tests/AdbHelpers.java
@@ -16,13 +16,17 @@
 package com.android.contacts.tests;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.os.Build;
 import android.os.Bundle;
+import android.preference.PreferenceManager;
 import android.support.annotation.RequiresApi;
+import android.support.test.InstrumentationRegistry;
 import android.util.Log;
 
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.preference.ContactsPreferences;
+import com.android.contacts.util.SharedPreferenceUtil;
 
 /**
  * Contains utility methods that can be invoked directly from adb using RunMethodInstrumentation.
@@ -73,4 +77,17 @@
     public static void clearDefaultAccount(Context context) {
         new ContactsPreferences(context).clearDefaultAccount();
     }
+
+    public static void clearPreferences(Context context) {
+        SharedPreferenceUtil.clear(context);
+    }
+
+    public static void dumpPreferences(Context context) {
+        Log.d(TAG, "preferences=" + getAppContext().getSharedPreferences(
+                getAppContext().getPackageName(), Context.MODE_PRIVATE).getAll());
+    }
+
+    private static Context getAppContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
 }