summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/src/com/android/contacts/NoPermissionsLaunchSmokeTest.java3
-rw-r--r--tests/src/com/android/contacts/common/database/SimContactDaoTests.java359
-rw-r--r--tests/src/com/android/contacts/common/model/SimContactTests.java43
-rw-r--r--tests/src/com/android/contacts/tests/AccountsTestHelper.java106
-rw-r--r--tests/src/com/android/contacts/tests/SimContactsTestHelper.java198
5 files changed, 709 insertions, 0 deletions
diff --git a/tests/src/com/android/contacts/NoPermissionsLaunchSmokeTest.java b/tests/src/com/android/contacts/NoPermissionsLaunchSmokeTest.java
index 8364b7b07..c9ea3b6aa 100644
--- a/tests/src/com/android/contacts/NoPermissionsLaunchSmokeTest.java
+++ b/tests/src/com/android/contacts/NoPermissionsLaunchSmokeTest.java
@@ -6,6 +6,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
+import android.support.test.filters.Suppress;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
@@ -36,6 +37,8 @@ import static org.junit.Assume.assumeTrue;
* -e class com.android.contacts.NoPermissionsLaunchSmokeTest
*/
@MediumTest
+// suppressed because failed assumptions are reported as test failures by the build server
+@Suppress
@RunWith(AndroidJUnit4.class)
public class NoPermissionsLaunchSmokeTest {
private static final long TIMEOUT = 5000;
diff --git a/tests/src/com/android/contacts/common/database/SimContactDaoTests.java b/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
new file mode 100644
index 000000000..5b25eb2b5
--- /dev/null
+++ b/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
@@ -0,0 +1,359 @@
+/*
+ * 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.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.database.DatabaseUtils;
+import android.provider.ContactsContract;
+import android.support.annotation.RequiresApi;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.contacts.common.model.SimContact;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.tests.AccountsTestHelper;
+import com.android.contacts.tests.SimContactsTestHelper;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static android.os.Build.VERSION_CODES;
+import static org.hamcrest.Matchers.allOf;
+import static org.junit.Assert.assertThat;
+
+import android.support.test.filters.Suppress;
+
+@RunWith(Enclosed.class)
+public class SimContactDaoTests {
+
+ // Lollipop MR1 required for AccountManager.removeAccountExplicitly
+ @RequiresApi(api = VERSION_CODES.LOLLIPOP_MR1)
+ @SdkSuppress(minSdkVersion = VERSION_CODES.LOLLIPOP_MR1)
+ @LargeTest
+ @RunWith(AndroidJUnit4.class)
+ public static class ImportIntegrationTest {
+ private AccountWithDataSet mAccount;
+ private AccountsTestHelper mAccountsHelper;
+ private ContentResolver mResolver;
+
+ @Before
+ public void setUp() throws Exception {
+ mAccountsHelper = new AccountsTestHelper();
+ mAccount = mAccountsHelper.addTestAccount();
+ mResolver = getContext().getContentResolver();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mAccountsHelper.cleanup();
+ }
+
+ @Test
+ public void importFromSim() throws Exception {
+ final SimContactDao sut = new SimContactDao(getContext());
+
+ sut.importContacts(Arrays.asList(
+ new SimContact(1, "Test One", "15095550101", null),
+ new SimContact(2, "Test Two", "15095550102", null),
+ new SimContact(3, "Test Three", "15095550103", new String[] {
+ "user@example.com", "user2@example.com"
+ })
+ ), mAccount);
+
+ Cursor cursor = queryContactWithName("Test One");
+ assertThat(cursor, hasCount(2));
+ assertThat(cursor, hasName("Test One"));
+ assertThat(cursor, hasPhone("15095550101"));
+ cursor.close();
+
+ cursor = queryContactWithName("Test Two");
+ assertThat(cursor, hasCount(2));
+ assertThat(cursor, hasName("Test Two"));
+ assertThat(cursor, hasPhone("15095550102"));
+ cursor.close();
+
+ cursor = queryContactWithName("Test Three");
+ assertThat(cursor, hasCount(4));
+ assertThat(cursor, hasName("Test Three"));
+ assertThat(cursor, hasPhone("15095550103"));
+ assertThat(cursor, allOf(hasEmail("user@example.com"), hasEmail("user2@example.com")));
+ cursor.close();
+ }
+
+ @Test
+ public void importContactWhichOnlyHasName() throws Exception {
+ final SimContactDao sut = new SimContactDao(getContext());
+
+ sut.importContacts(Arrays.asList(
+ new SimContact(1, "Test importJustName", null, null)
+ ), mAccount);
+
+ Cursor cursor = queryAllDataInAccount();
+
+ assertThat(cursor, hasCount(1));
+ assertThat(cursor, hasName("Test importJustName"));
+ cursor.close();
+ }
+
+ @Test
+ public void importContactWhichOnlyHasPhone() throws Exception {
+ final SimContactDao sut = new SimContactDao(getContext());
+
+ sut.importContacts(Arrays.asList(
+ new SimContact(1, null, "15095550111", null)
+ ), mAccount);
+
+ Cursor cursor = queryAllDataInAccount();
+
+ assertThat(cursor, hasCount(1));
+ assertThat(cursor, hasPhone("15095550111"));
+ cursor.close();
+ }
+
+ @Test
+ public void ignoresEmptyContacts() throws Exception {
+ final SimContactDao sut = new SimContactDao(getContext());
+
+ // This probably isn't possible but we'll test it to demonstrate expected behavior and
+ // just in case it does occur
+ sut.importContacts(Arrays.asList(
+ new SimContact(1, null, null, null),
+ new SimContact(2, null, null, null),
+ new SimContact(3, null, null, null),
+ new SimContact(4, "Not null", null, null)
+ ), mAccount);
+
+ final Cursor contactsCursor = queryAllRawContactsInAccount();
+ assertThat(contactsCursor, hasCount(1));
+ contactsCursor.close();
+
+ final Cursor dataCursor = queryAllDataInAccount();
+ assertThat(dataCursor, hasCount(1));
+
+ dataCursor.close();
+ }
+
+ private Cursor queryAllRawContactsInAccount() {
+ return new StringableCursor(mResolver.query(ContactsContract.RawContacts.CONTENT_URI, null,
+ ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
+ ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?",
+ new String[] {
+ mAccount.name,
+ mAccount.type
+ }, null));
+ }
+
+ private Cursor queryAllDataInAccount() {
+ return new StringableCursor(mResolver.query(ContactsContract.Data.CONTENT_URI, null,
+ ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
+ ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?",
+ new String[] {
+ mAccount.name,
+ mAccount.type
+ }, null));
+ }
+
+ private Cursor queryContactWithName(String name) {
+ return new StringableCursor(mResolver.query(ContactsContract.Data.CONTENT_URI, null,
+ ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
+ ContactsContract.RawContacts.ACCOUNT_TYPE+ "=? AND " +
+ ContactsContract.Data.DISPLAY_NAME + "=?",
+ new String[] {
+ mAccount.name,
+ mAccount.type,
+ name
+ }, null));
+ }
+ }
+
+ @LargeTest
+ // suppressed because failed assumptions are reported as test failures by the build server
+ @Suppress
+ @RunWith(AndroidJUnit4.class)
+ public static class ReadIntegrationTest {
+ private SimContactsTestHelper mSimTestHelper;
+ private ArrayList<ContentProviderOperation> mSimSnapshot;
+
+ @Before
+ public void setUp() throws Exception {
+ mSimTestHelper = new SimContactsTestHelper();
+
+ mSimTestHelper.assumeSimWritable();
+ if (!mSimTestHelper.isSimWritable()) return;
+
+ mSimSnapshot = mSimTestHelper.captureRestoreSnapshot();
+ mSimTestHelper.deleteAllSimContacts();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSimTestHelper.restore(mSimSnapshot);
+ }
+
+ @Test
+ public void readFromSim() {
+ mSimTestHelper.addSimContact("Test Simone", "15095550101");
+ mSimTestHelper.addSimContact("Test Simtwo", "15095550102");
+ mSimTestHelper.addSimContact("Test Simthree", "15095550103");
+
+ final SimContactDao sut = new SimContactDao(getContext());
+ final ArrayList<SimContact> contacts = sut.loadSimContacts();
+
+ assertThat(contacts.get(0), isSimContactWithNameAndPhone("Test Simone", "15095550101"));
+ assertThat(contacts.get(1), isSimContactWithNameAndPhone("Test Simtwo", "15095550102"));
+ assertThat(contacts.get(2), isSimContactWithNameAndPhone("Test Simthree", "15095550103"));
+ }
+ }
+
+ private static Matcher<SimContact> isSimContactWithNameAndPhone(final String name,
+ final String phone) {
+ return new BaseMatcher<SimContact>() {
+ @Override
+ public boolean matches(Object o) {
+ if (!(o instanceof SimContact)) return false;
+
+ SimContact other = (SimContact) o;
+
+ return name.equals(other.getName())
+ && phone.equals(other.getPhone());
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("SimContact with name=" + name + " and phone=" +
+ phone);
+ }
+ };
+ }
+
+ private static Matcher<Cursor> hasCount(final int count) {
+ return new BaseMatcher<Cursor>() {
+ @Override
+ public boolean matches(Object o) {
+ if (!(o instanceof Cursor)) return false;
+ return ((Cursor)o).getCount() == count;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Cursor with " + count + " rows");
+ }
+ };
+ }
+
+ private static Matcher<Cursor> hasMimeType(String type) {
+ return hasValueForColumn(ContactsContract.Data.MIMETYPE, type);
+ }
+
+ private static Matcher<Cursor> hasValueForColumn(final String column, final String value) {
+ return new BaseMatcher<Cursor>() {
+
+ @Override
+ public boolean matches(Object o) {
+ if (!(o instanceof Cursor)) return false;
+ final Cursor cursor = (Cursor)o;
+
+ final int index = cursor.getColumnIndexOrThrow(column);
+ return value.equals(cursor.getString(index));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Cursor with " + column + "=" + value);
+ }
+ };
+ }
+
+ private static Matcher<Cursor> hasRowMatching(final Matcher<Cursor> rowMatcher) {
+ return new BaseMatcher<Cursor>() {
+ @Override
+ public boolean matches(Object o) {
+ if (!(o instanceof Cursor)) return false;
+ final Cursor cursor = (Cursor)o;
+
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ if (rowMatcher.matches(cursor)) return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Cursor with row matching ");
+ rowMatcher.describeTo(description);
+ }
+ };
+ }
+
+ private static Matcher<Cursor> hasName(final String name) {
+ return hasRowMatching(allOf(
+ hasMimeType(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE),
+ hasValueForColumn(
+ ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name)));
+ }
+
+ private static Matcher<Cursor> hasPhone(final String phone) {
+ return hasRowMatching(allOf(
+ hasMimeType(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE),
+ hasValueForColumn(
+ ContactsContract.CommonDataKinds.Phone.NUMBER, phone)));
+ }
+
+ private static Matcher<Cursor> hasEmail(final String email) {
+ return hasRowMatching(allOf(
+ hasMimeType(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE),
+ hasValueForColumn(
+ ContactsContract.CommonDataKinds.Email.ADDRESS, email)));
+ }
+
+ static class StringableCursor extends CursorWrapper {
+ public StringableCursor(Cursor cursor) {
+ super(cursor);
+ }
+
+ @Override
+ public String toString() {
+ final Cursor wrapped = getWrappedCursor();
+
+ if (wrapped.getCount() == 0) {
+ return "Empty Cursor";
+ }
+
+ return DatabaseUtils.dumpCursorToString(wrapped);
+ }
+ }
+
+ static Context getContext() {
+ return InstrumentationRegistry.getTargetContext();
+ }
+}
diff --git a/tests/src/com/android/contacts/common/model/SimContactTests.java b/tests/src/com/android/contacts/common/model/SimContactTests.java
new file mode 100644
index 000000000..de9ab5a1f
--- /dev/null
+++ b/tests/src/com/android/contacts/common/model/SimContactTests.java
@@ -0,0 +1,43 @@
+package com.android.contacts.common.model;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Created by mhagerott on 10/6/16.
+ */
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SimContactTests {
+ @Test
+ public void parcelRoundtrip() {
+ assertParcelsCorrectly(new SimContact(1, "name1", "phone1",
+ new String[] { "email1a", "email1b" }));
+ assertParcelsCorrectly(new SimContact(2, "name2", "phone2", null));
+ assertParcelsCorrectly(new SimContact(3, "name3", null,
+ new String[] { "email3" }));
+ assertParcelsCorrectly(new SimContact(4, null, "phone4",
+ new String[] { "email4" }));
+ assertParcelsCorrectly(new SimContact(5, null, null, null));
+ assertParcelsCorrectly(new SimContact(6, "name6", "phone6",
+ new String[0]));
+ }
+
+ private void assertParcelsCorrectly(SimContact contact) {
+ final Parcel parcel = Parcel.obtain();
+ parcel.writeParcelable(contact, 0);
+ parcel.setDataPosition(0);
+ final SimContact unparceled = parcel.readParcelable(
+ SimContact.class.getClassLoader());
+ assertThat(unparceled, equalTo(contact));
+ parcel.recycle();
+ }
+}
diff --git a/tests/src/com/android/contacts/tests/AccountsTestHelper.java b/tests/src/com/android/contacts/tests/AccountsTestHelper.java
new file mode 100644
index 000000000..be826f7ec
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/AccountsTestHelper.java
@@ -0,0 +1,106 @@
+/*
+ * 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.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Build;
+import android.provider.ContactsContract.RawContacts;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresApi;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.contacts.common.model.account.AccountWithDataSet;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+@SuppressWarnings("MissingPermission")
+public class AccountsTestHelper {
+ public static final String TEST_ACCOUNT_TYPE = "com.android.contacts.tests.testauth.basic";
+
+ private final Context mContext;
+ private final AccountManager mAccountManager;
+ private final ContentResolver mResolver;
+
+ private Account mTestAccount;
+
+ public AccountsTestHelper() {
+ // Use context instead of target context because the test package has the permissions needed
+ // to add and remove accounts.
+ this(InstrumentationRegistry.getContext());
+ }
+
+ public AccountsTestHelper(Context context) {
+ mContext = context;
+ mAccountManager = AccountManager.get(mContext);
+ mResolver = mContext.getContentResolver();
+ }
+
+ public AccountWithDataSet addTestAccount() {
+ return addTestAccount(generateAccountName());
+ }
+
+ public String generateAccountName(String prefix) {
+ return prefix + "_t" + System.nanoTime();
+ }
+
+ public String generateAccountName() {
+ return generateAccountName("test");
+ }
+
+ public AccountWithDataSet addTestAccount(@NonNull String name) {
+ // remember the most recent one. If the caller wants to add multiple accounts they will
+ // have to keep track of them themselves.
+ mTestAccount = new Account(name, TEST_ACCOUNT_TYPE);
+ assertTrue(mAccountManager.addAccountExplicitly(mTestAccount, null, null));
+ return convertTestAccount();
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
+ public void removeTestAccount(AccountWithDataSet account) {
+ final Account remove = account.getAccountOrNull();
+ mAccountManager.removeAccountExplicitly(remove);
+ }
+
+ public void removeContactsForAccount() {
+ // Not sure if this is necessary or if contacts are automatically cleaned up when the
+ // account is removed.
+ mResolver.delete(RawContacts.CONTENT_URI,
+ RawContacts.ACCOUNT_NAME + "=? AND " + RawContacts.ACCOUNT_TYPE + "=?",
+ new String[] { mTestAccount.name, mTestAccount.type });
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
+ public void cleanup() {
+ assertNotNull(mTestAccount);
+
+ // Note that we don't need to cleanup up the contact data associated with the account.
+ // CP2 will eventually do that automatically so as long as we're using unique account
+ // names we should be safe. Note that cleanup is not done synchronously when the account
+ // is removed so if multiple tests are using the same account name then the data should
+ // be manually deleted after each test run.
+
+ mAccountManager.removeAccountExplicitly(mTestAccount);
+ mTestAccount = null;
+ }
+
+ private AccountWithDataSet convertTestAccount() {
+ return new AccountWithDataSet(mTestAccount.name, mTestAccount.type, null);
+ }
+}
diff --git a/tests/src/com/android/contacts/tests/SimContactsTestHelper.java b/tests/src/com/android/contacts/tests/SimContactsTestHelper.java
new file mode 100644
index 000000000..45ac8d96f
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/SimContactsTestHelper.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.tests;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.telephony.TelephonyManager;
+
+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;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+
+public class SimContactsTestHelper {
+
+ private final Context mContext;
+ private final TelephonyManager mTelephonyManager;
+ private final ContentResolver mResolver;
+ private final SimContactDao mSimDao;
+
+ public SimContactsTestHelper() {
+ this(InstrumentationRegistry.getTargetContext());
+ }
+
+ public SimContactsTestHelper(Context context) {
+ mContext = context;
+ mResolver = context.getContentResolver();
+ mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ mSimDao = new SimContactDao(context);
+ }
+
+ public int getSimContactCount() {
+ Cursor cursor = mContext.getContentResolver().query(SimContactDao.ICC_CONTENT_URI,
+ null, null, null, null);
+ try {
+ return cursor.getCount();
+ } finally {
+ cursor.close();
+ }
+ }
+
+ 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
+ // when inserting.
+ if (name != null) {
+ values.put("tag", name);
+ }
+ if (number != null) {
+ values.put(SimContactDao.NUMBER, number);
+ }
+ return mResolver.insert(SimContactDao.ICC_CONTENT_URI, values);
+ }
+
+ public ContentProviderResult[] deleteAllSimContacts()
+ throws RemoteException, OperationApplicationException {
+ SimContactDao dao = new SimContactDao(mContext);
+ List<SimContact> contacts = dao.loadSimContacts();
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ for (SimContact contact : contacts) {
+ ops.add(ContentProviderOperation
+ .newDelete(SimContactDao.ICC_CONTENT_URI)
+ .withSelection(getWriteSelection(contact), null)
+ .build());
+ }
+ return mResolver.applyBatch(SimContactDao.ICC_CONTENT_URI.getAuthority(), ops);
+ }
+
+ public ContentProviderResult[] restore(ArrayList<ContentProviderOperation> restoreOps)
+ throws RemoteException, OperationApplicationException {
+ if (restoreOps == null) return null;
+
+ // 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);
+ }
+
+ public ArrayList<ContentProviderOperation> captureRestoreSnapshot() {
+ ArrayList<SimContact> contacts = mSimDao.loadSimContacts();
+
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ for (SimContact contact : contacts) {
+ final String[] emails = contact.getEmails();
+ if (emails != null && emails.length > 0) {
+ throw new IllegalStateException("Cannot restore emails." +
+ " Please manually remove SIM contacts with emails.");
+ }
+ ops.add(ContentProviderOperation
+ .newInsert(SimContactDao.ICC_CONTENT_URI)
+ .withValue("tag", contact.getName())
+ .withValue("number", contact.getPhone())
+ .build());
+ }
+ return ops;
+ }
+
+ public String getWriteSelection(SimContact simContact) {
+ return "tag='" + simContact.getName() + "' AND " + SimContactDao.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);
+ }
+
+ public boolean isSimReady() {
+ return mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY;
+ }
+
+ public boolean doesSimHaveContacts() {
+ return isSimReady() && getSimContactCount() > 0;
+ }
+
+ public boolean isSimWritable() {
+ if (!isSimReady()) return false;
+ final String name = "writabeProbe" + System.nanoTime();
+ final Uri uri = addSimContact(name, "15095550101");
+ return uri != null && deleteSimContact(name, "15095550101") == 1;
+ }
+
+ public void assumeSimReady() {
+ assumeTrue(isSimReady());
+ }
+
+ public void assumeHasSimContacts() {
+ assumeTrue(doesSimHaveContacts());
+ }
+
+ public void assumeSimCardAbsent() {
+ assumeThat(mTelephonyManager.getSimState(), equalTo(TelephonyManager.SIM_STATE_ABSENT));
+ }
+
+ // The emulator reports SIM_STATE_READY but writes are ignored. This verifies that the
+ // device will actually persist writes to the SIM card.
+ public void assumeSimWritable() {
+ assumeSimReady();
+ assumeTrue(isSimWritable());
+ }
+}