DO NOT MERGE Improve testability of SIM import code.
For Iec6eb441fe197daceb24e87288f8e0c5ac0ce2cf
test
ran GoogleContactsTests
Change-Id: I7d34e68f389143f94c26190cd9b3206ca871c64e
(cherry picked from commit 52d8cae888457d9639696a19db29a2ff36deca85)
diff --git a/src/com/android/contacts/SimImportService.java b/src/com/android/contacts/SimImportService.java
index 7a4997e..4fa3695 100644
--- a/src/com/android/contacts/SimImportService.java
+++ b/src/com/android/contacts/SimImportService.java
@@ -32,6 +32,7 @@
import com.android.contacts.activities.PeopleActivity;
import com.android.contacts.common.database.SimContactDao;
+import com.android.contacts.common.database.SimContactDaoImpl;
import com.android.contacts.common.model.SimCard;
import com.android.contacts.common.model.SimContact;
import com.android.contacts.common.model.account.AccountWithDataSet;
@@ -49,6 +50,26 @@
private static final String TAG = "SimImportService";
+ /**
+ * Wrapper around the service state for testability
+ */
+ public interface StatusProvider {
+
+ /**
+ * Returns whether there is any imports still pending
+ *
+ * <p>This should be called from the UI thread</p>
+ */
+ boolean isRunning();
+
+ /**
+ * Returns whether an import for sim has been requested
+ *
+ * <p>This should be called from the UI thread</p>
+ */
+ boolean isImporting(SimCard sim);
+ }
+
public static final String EXTRA_ACCOUNT = "account";
public static final String EXTRA_SIM_CONTACTS = "simContacts";
public static final String EXTRA_SIM_SUBSCRIPTION_ID = "simSubscriptionId";
@@ -74,12 +95,24 @@
// Keeps track of current tasks. This is only modified from the UI thread.
private static List<ImportTask> sPending = new ArrayList<>();
+ private static StatusProvider sStatusProvider = new StatusProvider() {
+ @Override
+ public boolean isRunning() {
+ return !sPending.isEmpty();
+ }
+
+ @Override
+ public boolean isImporting(SimCard sim) {
+ return SimImportService.isImporting(sim);
+ }
+ };
+
/**
* Returns whether an import for sim has been requested
*
* <p>This should be called from the UI thread</p>
*/
- public static boolean isImporting(SimCard sim) {
+ private static boolean isImporting(SimCard sim) {
for (ImportTask task : sPending) {
if (task.getSim().equals(sim)) {
return true;
@@ -88,13 +121,8 @@
return false;
}
- /**
- * Returns whether there is any imports still pending
- *
- * <p>This should be called from the UI thread</p>
- */
- public static boolean isRunning() {
- return !sPending.isEmpty();
+ public static StatusProvider getStatusProvider() {
+ return sStatusProvider;
}
/**
diff --git a/src/com/android/contacts/common/database/SimContactDao.java b/src/com/android/contacts/common/database/SimContactDao.java
index b5b6626..b7b3ae4 100644
--- a/src/com/android/contacts/common/database/SimContactDao.java
+++ b/src/com/android/contacts/common/database/SimContactDao.java
@@ -15,46 +15,19 @@
*/
package com.android.contacts.common.database;
-import android.annotation.TargetApi;
-import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.OperationApplicationException;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
import android.os.RemoteException;
-import android.provider.BaseColumns;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.support.annotation.VisibleForTesting;
-import android.support.v4.util.ArrayMap;
-import android.support.v4.util.ArraySet;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import android.util.SparseArray;
-import com.android.contacts.R;
-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.google.common.base.Joiner;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -63,349 +36,15 @@
* Provides data access methods for loading contacts from a SIM card and and migrating these
* SIM contacts to a CP2 account.
*/
-public class SimContactDao {
- private static final String TAG = "SimContactDao";
-
- // Maximum number of SIM contacts to import in a single ContentResolver.applyBatch call.
- // This is necessary to avoid TransactionTooLargeException when there are a large number of
- // contacts. This has been tested on Nexus 6 NME70B and is probably be conservative enough
- // to work on any phone.
- private static final int IMPORT_MAX_BATCH_SIZE = 300;
-
- // How many SIM contacts to consider in a single query. This prevents hitting the SQLite
- // query parameter limit.
- static final int QUERY_MAX_BATCH_SIZE = 100;
+public abstract class 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");
-
- public static String _ID = BaseColumns._ID;
- public static String NAME = "name";
- public static String NUMBER = "number";
- public static String EMAILS = "emails";
-
- private final Context mContext;
- private final ContentResolver mResolver;
- private final TelephonyManager mTelephonyManager;
-
- private SimContactDao(Context context) {
- mContext = context;
- mResolver = context.getContentResolver();
- mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- }
-
-
- public Context getContext() {
- return mContext;
- }
-
- public boolean canReadSimContacts() {
- // Require SIM_STATE_READY because the TelephonyManager methods related to SIM require
- // this state
- return hasTelephony() && hasPermissions() &&
- mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY;
- }
-
- public List<SimCard> getSimCards() {
- if (!canReadSimContacts()) {
- return Collections.emptyList();
- }
- final List<SimCard> sims = CompatUtils.isMSIMCompatible() ?
- getSimCardsFromSubscriptions() :
- Collections.singletonList(SimCard.create(mTelephonyManager,
- mContext.getString(R.string.single_sim_display_label)));
- return SharedPreferenceUtil.restoreSimStates(mContext, sims);
- }
-
- public List<SimCard> getSimCardsWithContacts() {
- final List<SimCard> result = new ArrayList<>();
- for (SimCard sim : getSimCards()) {
- result.add(sim.withContacts(loadContactsForSim(sim)));
- }
- return result;
- }
-
- public ArrayList<SimContact> loadContactsForSim(SimCard sim) {
- if (sim.hasValidSubscriptionId()) {
- return loadSimContacts(sim.getSubscriptionId());
- }
- return loadSimContacts();
- }
-
- public ArrayList<SimContact> loadSimContacts(int subscriptionId) {
- return loadFrom(ICC_CONTENT_URI.buildUpon()
- .appendPath("subId")
- .appendPath(String.valueOf(subscriptionId))
- .build());
- }
-
- public ArrayList<SimContact> loadSimContacts() {
- return loadFrom(ICC_CONTENT_URI);
- }
-
- public ContentProviderResult[] importContacts(List<SimContact> contacts,
- AccountWithDataSet targetAccount)
- throws RemoteException, OperationApplicationException {
- if (contacts.size() < IMPORT_MAX_BATCH_SIZE) {
- return importBatch(contacts, targetAccount);
- }
- final List<ContentProviderResult> results = new ArrayList<>();
- for (int i = 0; i < contacts.size(); i += IMPORT_MAX_BATCH_SIZE) {
- results.addAll(Arrays.asList(importBatch(
- contacts.subList(i, Math.min(contacts.size(), i + IMPORT_MAX_BATCH_SIZE)),
- targetAccount)));
- }
- return results.toArray(new ContentProviderResult[results.size()]);
- }
-
- 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 = SharedPreferenceUtil.restoreSimStates(mContext, 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;
- }
-
- /**
- * Finds SIM contacts that exist in CP2 and associates the account of the CP2 contact with
- * the SIM contact
- */
- public Map<AccountWithDataSet, Set<SimContact>> findAccountsOfExistingSimContacts(
- List<SimContact> contacts) {
- final Map<AccountWithDataSet, Set<SimContact>> result = new ArrayMap<>();
- for (int i = 0; i < contacts.size(); i += QUERY_MAX_BATCH_SIZE) {
- findAccountsOfExistingSimContacts(
- contacts.subList(i, Math.min(contacts.size(), i + QUERY_MAX_BATCH_SIZE)),
- result);
- }
- return result;
- }
-
- private void findAccountsOfExistingSimContacts(List<SimContact> contacts,
- Map<AccountWithDataSet, Set<SimContact>> result) {
- final Map<Long, List<SimContact>> rawContactToSimContact = new HashMap<>();
- Collections.sort(contacts, SimContact.compareByPhoneThenName());
-
- final Cursor dataCursor = queryRawContactsForSimContacts(contacts);
-
- try {
- while (dataCursor.moveToNext()) {
- final String number = DataQuery.getPhoneNumber(dataCursor);
- final String name = DataQuery.getDisplayName(dataCursor);
-
- final int index = SimContact.findByPhoneAndName(contacts, number, name);
- if (index < 0) {
- continue;
- }
- final SimContact contact = contacts.get(index);
- final long id = DataQuery.getRawContactId(dataCursor);
- if (!rawContactToSimContact.containsKey(id)) {
- rawContactToSimContact.put(id, new ArrayList<SimContact>());
- }
- rawContactToSimContact.get(id).add(contact);
- }
- } finally {
- dataCursor.close();
- }
-
- final Cursor accountsCursor = queryAccountsOfRawContacts(rawContactToSimContact.keySet());
- try {
- while (accountsCursor.moveToNext()) {
- final AccountWithDataSet account = AccountQuery.getAccount(accountsCursor);
- final long id = AccountQuery.getId(accountsCursor);
- if (!result.containsKey(account)) {
- result.put(account, new ArraySet<SimContact>());
- }
- for (SimContact contact : rawContactToSimContact.get(id)) {
- result.get(account).add(contact);
- }
- }
- } finally {
- accountsCursor.close();
- }
- }
-
- private ContentProviderResult[] importBatch(List<SimContact> contacts,
- AccountWithDataSet targetAccount)
- throws RemoteException, OperationApplicationException {
- final ArrayList<ContentProviderOperation> ops =
- createImportOperations(contacts, targetAccount);
- return mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
- }
-
- @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;
- }
-
- private List<SimContact> getContactsForSim(SimCard sim) {
- final List<SimContact> contacts = sim.getContacts();
- return contacts != null ? contacts : loadContactsForSim(sim);
- }
-
- // See b/32831092
- // Sometimes the SIM contacts provider seems to get stuck if read from multiple threads
- // concurrently. So we just have a global lock around it to prevent potential issues.
- private static final Object SIM_READ_LOCK = new Object();
- private ArrayList<SimContact> loadFrom(Uri uri) {
- synchronized (SIM_READ_LOCK) {
- final Cursor cursor = mResolver.query(uri, null, null, null, null);
-
- try {
- return loadFromCursor(cursor);
- } finally {
- cursor.close();
- }
- }
- }
-
- private ArrayList<SimContact> loadFromCursor(Cursor cursor) {
- final int colId = cursor.getColumnIndex(_ID);
- final int colName = cursor.getColumnIndex(NAME);
- final int colNumber = cursor.getColumnIndex(NUMBER);
- final int colEmails = cursor.getColumnIndex(EMAILS);
-
- final ArrayList<SimContact> result = new ArrayList<>();
-
- while (cursor.moveToNext()) {
- final long id = cursor.getLong(colId);
- final String name = cursor.getString(colName);
- final String number = cursor.getString(colNumber);
- final String emails = cursor.getString(colEmails);
-
- final SimContact contact = new SimContact(id, name, number, parseEmails(emails));
- result.add(contact);
- }
- return result;
- }
-
- private Cursor queryRawContactsForSimContacts(List<SimContact> contacts) {
- final StringBuilder selectionBuilder = new StringBuilder();
-
- int phoneCount = 0;
- int nameCount = 0;
- for (SimContact contact : contacts) {
- if (contact.hasPhone()) {
- phoneCount++;
- } else if (contact.hasName()) {
- nameCount++;
- }
- }
- List<String> selectionArgs = new ArrayList<>(phoneCount + 1);
-
- selectionBuilder.append('(');
- selectionBuilder.append(Data.MIMETYPE).append("=? AND ");
- selectionArgs.add(Phone.CONTENT_ITEM_TYPE);
-
- selectionBuilder.append(Phone.NUMBER).append(" IN (")
- .append(Joiner.on(',').join(Collections.nCopies(phoneCount, '?')))
- .append(')');
- for (SimContact contact : contacts) {
- if (contact.hasPhone()) {
- selectionArgs.add(contact.getPhone());
- }
- }
- selectionBuilder.append(')');
-
- if (nameCount > 0) {
- selectionBuilder.append(" OR (");
-
- selectionBuilder.append(Data.MIMETYPE).append("=? AND ");
- selectionArgs.add(StructuredName.CONTENT_ITEM_TYPE);
-
- selectionBuilder.append(Data.DISPLAY_NAME).append(" IN (")
- .append(Joiner.on(',').join(Collections.nCopies(nameCount, '?')))
- .append(')');
- for (SimContact contact : contacts) {
- if (!contact.hasPhone() && contact.hasName()) {
- selectionArgs.add(contact.getName());
- }
- }
- selectionBuilder.append(')');
- }
-
- return mResolver.query(Data.CONTENT_URI.buildUpon()
- .appendQueryParameter(Data.VISIBLE_CONTACTS_ONLY, "true")
- .build(),
- DataQuery.PROJECTION,
- selectionBuilder.toString(),
- selectionArgs.toArray(new String[selectionArgs.size()]),
- null);
- }
-
- private Cursor queryAccountsOfRawContacts(Set<Long> ids) {
- final StringBuilder selectionBuilder = new StringBuilder();
-
- final String[] args = new String[ids.size()];
-
- selectionBuilder.append(RawContacts._ID).append(" IN (")
- .append(Joiner.on(',').join(Collections.nCopies(args.length, '?')))
- .append(")");
- int i = 0;
- for (long id : ids) {
- args[i++] = String.valueOf(id);
- }
- return mResolver.query(RawContacts.CONTENT_URI,
- AccountQuery.PROJECTION,
- selectionBuilder.toString(),
- args,
- null);
- }
-
- private ArrayList<ContentProviderOperation> createImportOperations(List<SimContact> contacts,
- AccountWithDataSet targetAccount) {
- final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
- for (SimContact contact : contacts) {
- contact.appendCreateContactOperations(ops, targetAccount);
- }
- return ops;
- }
-
- private String[] parseEmails(String emails) {
- return emails != null ? emails.split(",") : null;
- }
-
- private boolean hasTelephony() {
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
- }
-
- private boolean hasPermissions() {
- return PermissionsUtil.hasContactsPermissions(mContext) &&
- PermissionsUtil.hasPhonePermissions(mContext);
- }
-
public static SimContactDao create(Context context) {
if (USE_FAKE_INSTANCE) {
- return new DebugImpl(context)
+ return new SimContactDaoImpl.DebugImpl(context)
.addSimCard(new SimCard("fake-sim-id1", 1, "Fake Carrier",
"Card 1", "15095550101", "us").withContacts(
new SimContact(1, "Sim One", "15095550111", null),
@@ -423,89 +62,27 @@
new SimContact(5, "Sim Duplicate", "15095550121", null)
));
}
- return new SimContactDao(context);
+ return new SimContactDaoImpl(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
- public static class DebugImpl extends SimContactDao {
+ public abstract boolean canReadSimContacts();
- private List<SimCard> mSimCards = new ArrayList<>();
- private SparseArray<SimCard> mCardsBySubscription = new SparseArray<>();
+ public abstract List<SimCard> getSimCards();
- public DebugImpl(Context context) {
- super(context);
- }
+ public abstract ArrayList<SimContact> loadContactsForSim(SimCard sim);
- public DebugImpl addSimCard(SimCard sim) {
- mSimCards.add(sim);
- mCardsBySubscription.put(sim.getSubscriptionId(), sim);
- return this;
- }
+ public abstract ContentProviderResult[] importContacts(List<SimContact> contacts,
+ AccountWithDataSet targetAccount)
+ throws RemoteException, OperationApplicationException;
- @Override
- public List<SimCard> getSimCards() {
- return SharedPreferenceUtil.restoreSimStates(getContext(), mSimCards);
- }
+ public abstract void persistSimStates(List<SimCard> simCards);
- @Override
- public ArrayList<SimContact> loadSimContacts() {
- return new ArrayList<>(mSimCards.get(0).getContacts());
- }
+ public abstract SimCard getSimBySubscriptionId(int subscriptionId);
- @Override
- public ArrayList<SimContact> loadSimContacts(int subscriptionId) {
- return new ArrayList<>(mCardsBySubscription.get(subscriptionId).getContacts());
- }
+ public abstract Map<AccountWithDataSet, Set<SimContact>> findAccountsOfExistingSimContacts(
+ List<SimContact> contacts);
- @Override
- public boolean canReadSimContacts() {
- return true;
- }
- }
-
- // Query used for detecting existing contacts that may match a SimContact.
- private static final class DataQuery {
-
- public static final String[] PROJECTION = new String[] {
- Data.RAW_CONTACT_ID, Phone.NUMBER, Data.DISPLAY_NAME, Data.MIMETYPE
- };
-
- public static final int RAW_CONTACT_ID = 0;
- public static final int PHONE_NUMBER = 1;
- public static final int DISPLAY_NAME = 2;
- public static final int MIMETYPE = 3;
-
- public static long getRawContactId(Cursor cursor) {
- return cursor.getLong(RAW_CONTACT_ID);
- }
-
- public static String getPhoneNumber(Cursor cursor) {
- return isPhoneNumber(cursor) ? cursor.getString(PHONE_NUMBER) : null;
- }
-
- public static String getDisplayName(Cursor cursor) {
- return cursor.getString(DISPLAY_NAME);
- }
-
- public static boolean isPhoneNumber(Cursor cursor) {
- return Phone.CONTENT_ITEM_TYPE.equals(cursor.getString(MIMETYPE));
- }
- }
-
- private static final class AccountQuery {
- public static final String[] PROJECTION = new String[] {
- RawContacts._ID, RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE,
- RawContacts.DATA_SET
- };
-
- public static long getId(Cursor cursor) {
- return cursor.getLong(0);
- }
-
- public static AccountWithDataSet getAccount(Cursor cursor) {
- return new AccountWithDataSet(cursor.getString(1), cursor.getString(2),
- cursor.getString(3));
- }
+ public void persistSimState(SimCard sim) {
+ persistSimStates(Collections.singletonList(sim));
}
}
diff --git a/src/com/android/contacts/common/database/SimContactDaoImpl.java b/src/com/android/contacts/common/database/SimContactDaoImpl.java
new file mode 100644
index 0000000..a6824bb
--- /dev/null
+++ b/src/com/android/contacts/common/database/SimContactDaoImpl.java
@@ -0,0 +1,473 @@
+/*
+ * 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.database;
+
+import android.annotation.TargetApi;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.RemoteException;
+import android.provider.BaseColumns;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.ArraySet;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.contacts.R;
+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.google.common.base.Joiner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Provides data access methods for loading contacts from a SIM card and and migrating these
+ * SIM contacts to a CP2 account.
+ */
+public class SimContactDaoImpl extends SimContactDao {
+ private static final String TAG = "SimContactDao";
+
+ // Maximum number of SIM contacts to import in a single ContentResolver.applyBatch call.
+ // This is necessary to avoid TransactionTooLargeException when there are a large number of
+ // contacts. This has been tested on Nexus 6 NME70B and is probably be conservative enough
+ // to work on any phone.
+ private static final int IMPORT_MAX_BATCH_SIZE = 300;
+
+ // How many SIM contacts to consider in a single query. This prevents hitting the SQLite
+ // query parameter limit.
+ static final int QUERY_MAX_BATCH_SIZE = 100;
+
+ @VisibleForTesting
+ public static final Uri ICC_CONTENT_URI = Uri.parse("content://icc/adn");
+
+ public static String _ID = BaseColumns._ID;
+ public static String NAME = "name";
+ public static String NUMBER = "number";
+ public static String EMAILS = "emails";
+
+ private final Context mContext;
+ private final ContentResolver mResolver;
+ private final TelephonyManager mTelephonyManager;
+
+ public SimContactDaoImpl(Context context) {
+ mContext = context;
+ mResolver = context.getContentResolver();
+ mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public boolean canReadSimContacts() {
+ // Require SIM_STATE_READY because the TelephonyManager methods related to SIM require
+ // this state
+ return hasTelephony() && hasPermissions() &&
+ mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY;
+ }
+
+ @Override
+ public List<SimCard> getSimCards() {
+ if (!canReadSimContacts()) {
+ return Collections.emptyList();
+ }
+ final List<SimCard> sims = CompatUtils.isMSIMCompatible() ?
+ getSimCardsFromSubscriptions() :
+ Collections.singletonList(SimCard.create(mTelephonyManager,
+ mContext.getString(R.string.single_sim_display_label)));
+ return SharedPreferenceUtil.restoreSimStates(mContext, sims);
+ }
+
+ @Override
+ public ArrayList<SimContact> loadContactsForSim(SimCard sim) {
+ if (sim.hasValidSubscriptionId()) {
+ return loadSimContacts(sim.getSubscriptionId());
+ }
+ return loadSimContacts();
+ }
+
+ public ArrayList<SimContact> loadSimContacts(int subscriptionId) {
+ return loadFrom(ICC_CONTENT_URI.buildUpon()
+ .appendPath("subId")
+ .appendPath(String.valueOf(subscriptionId))
+ .build());
+ }
+
+ public ArrayList<SimContact> loadSimContacts() {
+ return loadFrom(ICC_CONTENT_URI);
+ }
+
+ @Override
+ public ContentProviderResult[] importContacts(List<SimContact> contacts,
+ AccountWithDataSet targetAccount)
+ throws RemoteException, OperationApplicationException {
+ if (contacts.size() < IMPORT_MAX_BATCH_SIZE) {
+ return importBatch(contacts, targetAccount);
+ }
+ final List<ContentProviderResult> results = new ArrayList<>();
+ for (int i = 0; i < contacts.size(); i += IMPORT_MAX_BATCH_SIZE) {
+ results.addAll(Arrays.asList(importBatch(
+ contacts.subList(i, Math.min(contacts.size(), i + IMPORT_MAX_BATCH_SIZE)),
+ targetAccount)));
+ }
+ return results.toArray(new ContentProviderResult[results.size()]);
+ }
+
+ public void persistSimState(SimCard sim) {
+ SharedPreferenceUtil.persistSimStates(mContext, Collections.singletonList(sim));
+ }
+
+ @Override
+ public void persistSimStates(List<SimCard> simCards) {
+ SharedPreferenceUtil.persistSimStates(mContext, simCards);
+ }
+
+ @Override
+ public SimCard getSimBySubscriptionId(int subscriptionId) {
+ final List<SimCard> sims = SharedPreferenceUtil.restoreSimStates(mContext, 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;
+ }
+
+ /**
+ * Finds SIM contacts that exist in CP2 and associates the account of the CP2 contact with
+ * the SIM contact
+ */
+ public Map<AccountWithDataSet, Set<SimContact>> findAccountsOfExistingSimContacts(
+ List<SimContact> contacts) {
+ final Map<AccountWithDataSet, Set<SimContact>> result = new ArrayMap<>();
+ for (int i = 0; i < contacts.size(); i += QUERY_MAX_BATCH_SIZE) {
+ findAccountsOfExistingSimContacts(
+ contacts.subList(i, Math.min(contacts.size(), i + QUERY_MAX_BATCH_SIZE)),
+ result);
+ }
+ return result;
+ }
+
+ private void findAccountsOfExistingSimContacts(List<SimContact> contacts,
+ Map<AccountWithDataSet, Set<SimContact>> result) {
+ final Map<Long, List<SimContact>> rawContactToSimContact = new HashMap<>();
+ Collections.sort(contacts, SimContact.compareByPhoneThenName());
+
+ final Cursor dataCursor = queryRawContactsForSimContacts(contacts);
+
+ try {
+ while (dataCursor.moveToNext()) {
+ final String number = DataQuery.getPhoneNumber(dataCursor);
+ final String name = DataQuery.getDisplayName(dataCursor);
+
+ final int index = SimContact.findByPhoneAndName(contacts, number, name);
+ if (index < 0) {
+ continue;
+ }
+ final SimContact contact = contacts.get(index);
+ final long id = DataQuery.getRawContactId(dataCursor);
+ if (!rawContactToSimContact.containsKey(id)) {
+ rawContactToSimContact.put(id, new ArrayList<SimContact>());
+ }
+ rawContactToSimContact.get(id).add(contact);
+ }
+ } finally {
+ dataCursor.close();
+ }
+
+ final Cursor accountsCursor = queryAccountsOfRawContacts(rawContactToSimContact.keySet());
+ try {
+ while (accountsCursor.moveToNext()) {
+ final AccountWithDataSet account = AccountQuery.getAccount(accountsCursor);
+ final long id = AccountQuery.getId(accountsCursor);
+ if (!result.containsKey(account)) {
+ result.put(account, new ArraySet<SimContact>());
+ }
+ for (SimContact contact : rawContactToSimContact.get(id)) {
+ result.get(account).add(contact);
+ }
+ }
+ } finally {
+ accountsCursor.close();
+ }
+ }
+
+
+ private ContentProviderResult[] importBatch(List<SimContact> contacts,
+ AccountWithDataSet targetAccount)
+ throws RemoteException, OperationApplicationException {
+ final ArrayList<ContentProviderOperation> ops =
+ createImportOperations(contacts, targetAccount);
+ return mResolver.applyBatch(ContactsContract.AUTHORITY, ops);
+ }
+
+ @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;
+ }
+
+ private List<SimContact> getContactsForSim(SimCard sim) {
+ final List<SimContact> contacts = sim.getContacts();
+ return contacts != null ? contacts : loadContactsForSim(sim);
+ }
+
+ // See b/32831092
+ // Sometimes the SIM contacts provider seems to get stuck if read from multiple threads
+ // concurrently. So we just have a global lock around it to prevent potential issues.
+ private static final Object SIM_READ_LOCK = new Object();
+ private ArrayList<SimContact> loadFrom(Uri uri) {
+ synchronized (SIM_READ_LOCK) {
+ final Cursor cursor = mResolver.query(uri, null, null, null, null);
+
+ try {
+ return loadFromCursor(cursor);
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+
+ private ArrayList<SimContact> loadFromCursor(Cursor cursor) {
+ final int colId = cursor.getColumnIndex(_ID);
+ final int colName = cursor.getColumnIndex(NAME);
+ final int colNumber = cursor.getColumnIndex(NUMBER);
+ final int colEmails = cursor.getColumnIndex(EMAILS);
+
+ final ArrayList<SimContact> result = new ArrayList<>();
+
+ while (cursor.moveToNext()) {
+ final long id = cursor.getLong(colId);
+ final String name = cursor.getString(colName);
+ final String number = cursor.getString(colNumber);
+ final String emails = cursor.getString(colEmails);
+
+ final SimContact contact = new SimContact(id, name, number, parseEmails(emails));
+ result.add(contact);
+ }
+ return result;
+ }
+
+ private Cursor queryRawContactsForSimContacts(List<SimContact> contacts) {
+ final StringBuilder selectionBuilder = new StringBuilder();
+
+ int phoneCount = 0;
+ int nameCount = 0;
+ for (SimContact contact : contacts) {
+ if (contact.hasPhone()) {
+ phoneCount++;
+ } else if (contact.hasName()) {
+ nameCount++;
+ }
+ }
+ List<String> selectionArgs = new ArrayList<>(phoneCount + 1);
+
+ selectionBuilder.append('(');
+ selectionBuilder.append(Data.MIMETYPE).append("=? AND ");
+ selectionArgs.add(Phone.CONTENT_ITEM_TYPE);
+
+ selectionBuilder.append(Phone.NUMBER).append(" IN (")
+ .append(Joiner.on(',').join(Collections.nCopies(phoneCount, '?')))
+ .append(')');
+ for (SimContact contact : contacts) {
+ if (contact.hasPhone()) {
+ selectionArgs.add(contact.getPhone());
+ }
+ }
+ selectionBuilder.append(')');
+
+ if (nameCount > 0) {
+ selectionBuilder.append(" OR (");
+
+ selectionBuilder.append(Data.MIMETYPE).append("=? AND ");
+ selectionArgs.add(StructuredName.CONTENT_ITEM_TYPE);
+
+ selectionBuilder.append(Data.DISPLAY_NAME).append(" IN (")
+ .append(Joiner.on(',').join(Collections.nCopies(nameCount, '?')))
+ .append(')');
+ for (SimContact contact : contacts) {
+ if (!contact.hasPhone() && contact.hasName()) {
+ selectionArgs.add(contact.getName());
+ }
+ }
+ selectionBuilder.append(')');
+ }
+
+ return mResolver.query(Data.CONTENT_URI.buildUpon()
+ .appendQueryParameter(Data.VISIBLE_CONTACTS_ONLY, "true")
+ .build(),
+ DataQuery.PROJECTION,
+ selectionBuilder.toString(),
+ selectionArgs.toArray(new String[selectionArgs.size()]),
+ null);
+ }
+
+ private Cursor queryAccountsOfRawContacts(Set<Long> ids) {
+ final StringBuilder selectionBuilder = new StringBuilder();
+
+ final String[] args = new String[ids.size()];
+
+ selectionBuilder.append(RawContacts._ID).append(" IN (")
+ .append(Joiner.on(',').join(Collections.nCopies(args.length, '?')))
+ .append(")");
+ int i = 0;
+ for (long id : ids) {
+ args[i++] = String.valueOf(id);
+ }
+ return mResolver.query(RawContacts.CONTENT_URI,
+ AccountQuery.PROJECTION,
+ selectionBuilder.toString(),
+ args,
+ null);
+ }
+
+ private ArrayList<ContentProviderOperation> createImportOperations(List<SimContact> contacts,
+ AccountWithDataSet targetAccount) {
+ final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ for (SimContact contact : contacts) {
+ contact.appendCreateContactOperations(ops, targetAccount);
+ }
+ return ops;
+ }
+
+ private String[] parseEmails(String emails) {
+ return emails != null ? emails.split(",") : null;
+ }
+
+ private boolean hasTelephony() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+ }
+
+ private boolean hasPermissions() {
+ return PermissionsUtil.hasContactsPermissions(mContext) &&
+ PermissionsUtil.hasPhonePermissions(mContext);
+ }
+
+ // TODO remove this class and the USE_FAKE_INSTANCE flag once this code is not under
+ // active development or anytime after 3/1/2017
+ public static class DebugImpl extends SimContactDaoImpl {
+
+ 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 List<SimCard> getSimCards() {
+ return SharedPreferenceUtil.restoreSimStates(getContext(), mSimCards);
+ }
+
+ @Override
+ public ArrayList<SimContact> loadContactsForSim(SimCard card) {
+ return new ArrayList<>(card.getContacts());
+ }
+
+ @Override
+ public boolean canReadSimContacts() {
+ return true;
+ }
+ }
+
+ // Query used for detecting existing contacts that may match a SimContact.
+ private static final class DataQuery {
+
+ public static final String[] PROJECTION = new String[] {
+ Data.RAW_CONTACT_ID, Phone.NUMBER, Data.DISPLAY_NAME, Data.MIMETYPE
+ };
+
+ public static final int RAW_CONTACT_ID = 0;
+ public static final int PHONE_NUMBER = 1;
+ public static final int DISPLAY_NAME = 2;
+ public static final int MIMETYPE = 3;
+
+ public static long getRawContactId(Cursor cursor) {
+ return cursor.getLong(RAW_CONTACT_ID);
+ }
+
+ public static String getPhoneNumber(Cursor cursor) {
+ return isPhoneNumber(cursor) ? cursor.getString(PHONE_NUMBER) : null;
+ }
+
+ public static String getDisplayName(Cursor cursor) {
+ return cursor.getString(DISPLAY_NAME);
+ }
+
+ public static boolean isPhoneNumber(Cursor cursor) {
+ return Phone.CONTENT_ITEM_TYPE.equals(cursor.getString(MIMETYPE));
+ }
+ }
+
+ private static final class AccountQuery {
+ public static final String[] PROJECTION = new String[] {
+ RawContacts._ID, RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE,
+ RawContacts.DATA_SET
+ };
+
+ public static long getId(Cursor cursor) {
+ return cursor.getLong(0);
+ }
+
+ public static AccountWithDataSet getAccount(Cursor cursor) {
+ return new AccountWithDataSet(cursor.getString(1), cursor.getString(2),
+ cursor.getString(3));
+ }
+ }
+}
diff --git a/src/com/android/contacts/common/model/SimCard.java b/src/com/android/contacts/common/model/SimCard.java
index 7b13096..3e826dd 100644
--- a/src/com/android/contacts/common/model/SimCard.java
+++ b/src/com/android/contacts/common/model/SimCard.java
@@ -221,6 +221,20 @@
return result;
}
+ @Override
+ public String toString() {
+ return "SimCard{" +
+ "mSimId='" + mSimId + '\'' +
+ ", mSubscriptionId=" + mSubscriptionId +
+ ", mCarrierName=" + mCarrierName +
+ ", mDisplayName=" + mDisplayName +
+ ", mPhoneNumber='" + mPhoneNumber + '\'' +
+ ", mCountryCode='" + mCountryCode + '\'' +
+ ", mDismissed=" + mDismissed +
+ ", mImported=" + mImported +
+ ", mContacts=" + mContacts +
+ '}';
+ }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
public static SimCard create(SubscriptionInfo info) {
diff --git a/tests/src/com/android/contacts/common/database/SimContactDaoTests.java b/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
index ec03f0f..e180ca2 100644
--- a/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
+++ b/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
@@ -30,6 +30,7 @@
import android.support.test.filters.Suppress;
import android.support.test.runner.AndroidJUnit4;
+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.tests.AccountsTestHelper;
@@ -281,7 +282,8 @@
mSimTestHelper.addSimContact("Test Simthree", "15095550103");
final SimContactDao sut = SimContactDao.create(getContext());
- final ArrayList<SimContact> contacts = sut.loadSimContacts();
+ final SimCard sim = sut.getSimCards().get(0);
+ final ArrayList<SimContact> contacts = sut.loadContactsForSim(sim);
assertThat(contacts.get(0), isSimContactWithNameAndPhone("Test Simone", "15095550101"));
assertThat(contacts.get(1), isSimContactWithNameAndPhone("Test Simtwo", "15095550102"));
diff --git a/tests/src/com/android/contacts/tests/FakeSimContactDao.java b/tests/src/com/android/contacts/tests/FakeSimContactDao.java
new file mode 100644
index 0000000..ba091ca
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/FakeSimContactDao.java
@@ -0,0 +1,111 @@
+/*
+ * 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.tests;
+
+import android.content.ContentProviderResult;
+import android.content.OperationApplicationException;
+import android.os.RemoteException;
+
+import com.android.contacts.common.database.SimContactDao;
+import com.android.contacts.common.model.SimCard;
+import com.android.contacts.common.model.SimContact;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Fake implementation of SimContactDao for testing
+ */
+public class FakeSimContactDao extends SimContactDao {
+
+ public boolean canReadSimContacts = true;
+ public List<SimCard> simCards;
+ public Map<SimCard, ArrayList<SimContact>> simContacts;
+ public ContentProviderResult[] importResult;
+ public Map<AccountWithDataSet, Set<SimContact>> existingSimContacts;
+
+ public FakeSimContactDao() {
+ simCards = new ArrayList<>();
+ simContacts = new HashMap<>();
+ importResult = new ContentProviderResult[0];
+ existingSimContacts = new HashMap<>();
+ }
+
+ @Override
+ public boolean canReadSimContacts() {
+ return canReadSimContacts;
+ }
+
+ @Override
+ public List<SimCard> getSimCards() {
+ return simCards;
+ }
+
+ @Override
+ public ArrayList<SimContact> loadContactsForSim(SimCard sim) {
+ return simContacts.get(sim);
+ }
+
+ @Override
+ public ContentProviderResult[] importContacts(List<SimContact> contacts,
+ AccountWithDataSet targetAccount)
+ throws RemoteException, OperationApplicationException {
+ return importResult;
+ }
+
+ @Override
+ public void persistSimStates(List<SimCard> simCards) {
+ this.simCards = simCards;
+ }
+
+ @Override
+ public SimCard getSimBySubscriptionId(int subscriptionId) {
+ for (SimCard sim : simCards) {
+ if (sim.getSubscriptionId() == subscriptionId) {
+ return sim;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Map<AccountWithDataSet, Set<SimContact>> findAccountsOfExistingSimContacts(
+ List<SimContact> contacts) {
+ return existingSimContacts;
+ }
+
+ public FakeSimContactDao addSim(SimCard sim, SimContact... contacts) {
+ simCards.add(sim);
+ simContacts.put(sim, new ArrayList<>(Arrays.asList(contacts)));
+ return this;
+ }
+
+ public static FakeSimContactDao singleSimWithContacts(SimCard sim, SimContact... contacts) {
+ return new FakeSimContactDao().addSim(sim, contacts);
+ }
+
+ public static FakeSimContactDao noSim() {
+ FakeSimContactDao result = new FakeSimContactDao();
+ result.canReadSimContacts = false;
+ return result;
+ }
+
+}
diff --git a/tests/src/com/android/contacts/tests/SimContactsTestHelper.java b/tests/src/com/android/contacts/tests/SimContactsTestHelper.java
index c2ffead..552d790 100644
--- a/tests/src/com/android/contacts/tests/SimContactsTestHelper.java
+++ b/tests/src/com/android/contacts/tests/SimContactsTestHelper.java
@@ -15,7 +15,6 @@
*/
package com.android.contacts.tests;
-import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
@@ -29,9 +28,10 @@
import android.support.test.InstrumentationRegistry;
import android.telephony.TelephonyManager;
+import com.android.contacts.common.database.SimContactDaoImpl;
+import com.android.contacts.common.model.SimCard;
import com.android.contacts.common.model.SimContact;
import com.android.contacts.common.database.SimContactDao;
-import com.android.contacts.common.test.mocks.MockContentProvider;
import java.util.ArrayList;
import java.util.List;
@@ -59,7 +59,7 @@
}
public int getSimContactCount() {
- Cursor cursor = mContext.getContentResolver().query(SimContactDao.ICC_CONTENT_URI,
+ Cursor cursor = mContext.getContentResolver().query(SimContactDaoImpl.ICC_CONTENT_URI,
null, null, null, null);
try {
return cursor.getCount();
@@ -68,32 +68,6 @@
}
}
- public ContentValues iccRow(long id, String name, String number, String emails) {
- ContentValues values = new ContentValues();
- values.put(SimContactDao._ID, id);
- values.put(SimContactDao.NAME, name);
- values.put(SimContactDao.NUMBER, number);
- values.put(SimContactDao.EMAILS, emails);
- return values;
- }
-
- public ContentProvider iccProviderExpectingNoQueries() {
- return new MockContentProvider();
- }
-
- public ContentProvider emptyIccProvider() {
- final MockContentProvider provider = new MockContentProvider();
- provider.expectQuery(SimContactDao.ICC_CONTENT_URI)
- .withDefaultProjection(
- SimContactDao._ID, SimContactDao.NAME,
- SimContactDao.NUMBER, SimContactDao.EMAILS)
- .withAnyProjection()
- .withAnySelection()
- .withAnySortOrder()
- .returnEmptyCursor();
- return provider;
- }
-
public Uri addSimContact(String name, String number) {
ContentValues values = new ContentValues();
// Oddly even though it's called name when querying we have to use "tag" for it to work
@@ -102,23 +76,26 @@
values.put("tag", name);
}
if (number != null) {
- values.put(SimContactDao.NUMBER, number);
+ values.put(SimContactDaoImpl.NUMBER, number);
}
- return mResolver.insert(SimContactDao.ICC_CONTENT_URI, values);
+ return mResolver.insert(SimContactDaoImpl.ICC_CONTENT_URI, values);
}
public ContentProviderResult[] deleteAllSimContacts()
throws RemoteException, OperationApplicationException {
- SimContactDao dao = SimContactDao.create(mContext);
- List<SimContact> contacts = dao.loadSimContacts();
+ final List<SimCard> sims = mSimDao.getSimCards();
+ if (sims.isEmpty()) {
+ throw new IllegalStateException("Expected SIM card");
+ }
+ final List<SimContact> contacts = mSimDao.loadContactsForSim(sims.get(0));
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
for (SimContact contact : contacts) {
ops.add(ContentProviderOperation
- .newDelete(SimContactDao.ICC_CONTENT_URI)
+ .newDelete(SimContactDaoImpl.ICC_CONTENT_URI)
.withSelection(getWriteSelection(contact), null)
.build());
}
- return mResolver.applyBatch(SimContactDao.ICC_CONTENT_URI.getAuthority(), ops);
+ return mResolver.applyBatch(SimContactDaoImpl.ICC_CONTENT_URI.getAuthority(), ops);
}
public ContentProviderResult[] restore(ArrayList<ContentProviderOperation> restoreOps)
@@ -128,13 +105,17 @@
// Remove SIM contacts because we assume that caller wants the data to be in the exact
// state as when the restore ops were captured.
deleteAllSimContacts();
- return mResolver.applyBatch(SimContactDao.ICC_CONTENT_URI.getAuthority(), restoreOps);
+ return mResolver.applyBatch(SimContactDaoImpl.ICC_CONTENT_URI.getAuthority(), restoreOps);
}
public ArrayList<ContentProviderOperation> captureRestoreSnapshot() {
- ArrayList<SimContact> contacts = mSimDao.loadSimContacts();
+ final List<SimCard> sims = mSimDao.getSimCards();
+ if (sims.isEmpty()) {
+ throw new IllegalStateException("Expected SIM card");
+ }
+ final ArrayList<SimContact> contacts = mSimDao.loadContactsForSim(sims.get(0));
- ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
for (SimContact contact : contacts) {
final String[] emails = contact.getEmails();
if (emails != null && emails.length > 0) {
@@ -142,7 +123,7 @@
" Please manually remove SIM contacts with emails.");
}
ops.add(ContentProviderOperation
- .newInsert(SimContactDao.ICC_CONTENT_URI)
+ .newInsert(SimContactDaoImpl.ICC_CONTENT_URI)
.withValue("tag", contact.getName())
.withValue("number", contact.getPhone())
.build());
@@ -151,15 +132,15 @@
}
public String getWriteSelection(SimContact simContact) {
- return "tag='" + simContact.getName() + "' AND " + SimContactDao.NUMBER + "='" +
+ return "tag='" + simContact.getName() + "' AND " + SimContactDaoImpl.NUMBER + "='" +
simContact.getPhone() + "'";
}
public int deleteSimContact(@NonNull String name, @NonNull String number) {
// IccProvider doesn't use the selection args.
final String selection = "tag='" + name + "' AND " +
- SimContactDao.NUMBER + "='" + number + "'";
- return mResolver.delete(SimContactDao.ICC_CONTENT_URI, selection, null);
+ SimContactDaoImpl.NUMBER + "='" + number + "'";
+ return mResolver.delete(SimContactDaoImpl.ICC_CONTENT_URI, selection, null);
}
public boolean isSimReady() {