Merge "Added 'new' badge to navigation drawer." into ub-contactsdialer-h-dev
diff --git a/proguard.flags b/proguard.flags
index e50e640..e918c62 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -44,6 +44,7 @@
-keep class com.android.contacts.common.model.BuilderWrapper { *; }
-keep class com.android.contacts.common.model.Contact { *; }
-keep class com.android.contacts.common.model.ContactLoader { *; }
+-keep class com.android.contacts.common.model.Cp2DeviceLocalAccountLocator { *; }
-keep class com.android.contacts.common.model.CPOWrapper { *; }
-keep class com.android.contacts.common.model.dataitem.DataItem { *; }
-keep class com.android.contacts.common.model.dataitem.DataKind { *; }
diff --git a/res/layout/fragment_sim_import.xml b/res/layout/fragment_sim_import.xml
index 95864c8..2da988e 100644
--- a/res/layout/fragment_sim_import.xml
+++ b/res/layout/fragment_sim_import.xml
@@ -49,7 +49,6 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="16dp"
android:background="?android:colorBackground"
android:elevation="4dp">
@@ -63,7 +62,9 @@
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_height="match_parent"
+ android:paddingTop="8dp"
+ android:clipToPadding="false"/>
<android.support.v4.widget.ContentLoadingProgressBar
android:id="@+id/loading_progress"
diff --git a/src/com/android/contacts/SimImportFragment.java b/src/com/android/contacts/SimImportFragment.java
index bc20d62..c43caaa 100644
--- a/src/com/android/contacts/SimImportFragment.java
+++ b/src/com/android/contacts/SimImportFragment.java
@@ -26,6 +26,7 @@
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
+import android.support.v4.util.ArrayMap;
import android.support.v4.widget.ContentLoadingProgressBar;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
@@ -48,6 +49,8 @@
import com.android.contacts.editor.AccountHeaderPresenter;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@@ -56,7 +59,7 @@
* account
*/
public class SimImportFragment extends DialogFragment
- implements LoaderManager.LoaderCallbacks<ArrayList<SimContact>>,
+ implements LoaderManager.LoaderCallbacks<SimImportFragment.LoaderResult>,
MultiSelectEntryContactListAdapter.SelectedContactsListener {
private static final String KEY_SELECTED_IDS = "selectedIds";
@@ -86,6 +89,8 @@
mAccountTypeManager = AccountTypeManager.getInstance(getActivity());
mAdapter = new SimContactAdapter(getActivity());
+ // This needs to be set even though photos aren't loaded because the adapter assumes it
+ // will be non-null
mAdapter.setPhotoLoader(ContactPhotoManager.getInstance(getActivity()));
mAdapter.setDisplayCheckBoxes(true);
mAdapter.setHasHeader(0, false);
@@ -127,13 +132,20 @@
.getDefaultOrBestFallback(mPreferences, mAccountTypeManager);
mAccountHeaderPresenter.setCurrentAccount(currentDefaultAccount);
}
+ mAccountHeaderPresenter.setObserver(new AccountHeaderPresenter.Observer() {
+ @Override
+ public void onChange(AccountHeaderPresenter sender) {
+ mAdapter.setAccount(sender.getCurrentAccount());
+ }
+ });
+ mAdapter.setAccount(mAccountHeaderPresenter.getCurrentAccount());
mListView = (ListView) view.findViewById(R.id.list);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (mAdapter.existsInContacts(position)) {
+ if (mAdapter.existsInCurrentAccount(position)) {
Snackbar.make(getView(), R.string.sim_import_contact_exists_toast,
Snackbar.LENGTH_LONG).show();
} else {
@@ -178,7 +190,7 @@
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mAccountHeaderPresenter.onSaveInstanceState(outState);
- if (mAdapter != null) {
+ if (mAdapter != null && mAdapter.mContacts != null) {
outState.putLongArray(KEY_SELECTED_IDS, mAdapter.getSelectedContactIdsArray());
}
}
@@ -189,14 +201,14 @@
}
@Override
- public void onLoadFinished(Loader<ArrayList<SimContact>> loader,
- ArrayList<SimContact> data) {
+ public void onLoadFinished(Loader<LoaderResult> loader,
+ LoaderResult data) {
mLoadingIndicator.hide();
mListView.setEmptyView(getView().findViewById(R.id.empty_message));
if (data == null) {
return;
}
- mAdapter.setContacts(data);
+ mAdapter.setData(data);
if (mSelectedContacts != null) {
mAdapter.select(mSelectedContacts);
} else {
@@ -205,7 +217,7 @@
}
@Override
- public void onLoaderReset(Loader<ArrayList<SimContact>> loader) {
+ public void onLoaderReset(Loader<LoaderResult> loader) {
}
private void importCurrentSelections() {
@@ -267,7 +279,9 @@
private static class SimContactAdapter extends ContactListAdapter {
private ArrayList<SimContact> mContacts;
- private static float DISABLED_AVATAR_ALPHA = 0.38f;
+ private AccountWithDataSet mSelectedAccount;
+ private Map<AccountWithDataSet, Set<SimContact>> mExistingMap;
+ private Map<AccountWithDataSet, TreeSet<Long>> mPerAccountCheckedIds = new ArrayMap<>();
public SimContactAdapter(Context context) {
super(context);
@@ -282,21 +296,44 @@
super.bindView(itemView, partition, cursor, position);
ContactListItemView contactView = (ContactListItemView) itemView;
bindNameAndViewId(contactView, cursor);
- bindPhoto(contactView, partition, cursor);
// For accessibility. Tapping the item checks this so we don't need it to be separately
// clickable
contactView.getCheckBox().setFocusable(false);
contactView.getCheckBox().setClickable(false);
- setViewEnabled(contactView, !mContacts.get(cursor.getPosition()).existsInContacts());
+ setViewEnabled(contactView, !existsInCurrentAccount(position));
}
- public void setContacts(ArrayList<SimContact> contacts) {
- mContacts = contacts;
+ public void setData(LoaderResult result) {
+ mContacts = result.contacts;
+ mExistingMap = result.accountsMap;
changeCursor(SimContact.convertToContactsCursor(mContacts,
ContactQuery.CONTACT_PROJECTION_PRIMARY));
}
+ public void setAccount(AccountWithDataSet account) {
+ if (mContacts == null) {
+ mSelectedAccount = account;
+ return;
+ }
+
+ // Save the checked state for the current account.
+ if (mSelectedAccount != null) {
+ mPerAccountCheckedIds.put(mSelectedAccount, getSelectedContactIds());
+ }
+
+ mSelectedAccount = account;
+
+ TreeSet<Long> checked = mPerAccountCheckedIds.get(mSelectedAccount);
+ if (checked == null) {
+ checked = getEnabledIdsForCurrentAccount();
+ mPerAccountCheckedIds.put(mSelectedAccount, checked);
+ }
+ setSelectedContactIds(checked);
+
+ notifyDataSetChanged();
+ }
+
public ArrayList<SimContact> getSelectedContacts() {
if (mContacts == null) return null;
@@ -315,7 +352,7 @@
final TreeSet<Long> selected = new TreeSet<>();
for (SimContact contact : mContacts) {
- if (!contact.existsInContacts()) {
+ if (!existsInCurrentAccount(contact)) {
selected.add(contact.getId());
}
}
@@ -330,21 +367,38 @@
setSelectedContactIds(selected);
}
- public boolean existsInContacts(int position) {
- return mContacts.get(position).existsInContacts();
+ public boolean existsInCurrentAccount(int position) {
+ return existsInCurrentAccount(mContacts.get(position));
+ }
+
+ public boolean existsInCurrentAccount(SimContact contact) {
+ if (mSelectedAccount == null || !mExistingMap.containsKey(mSelectedAccount)) {
+ return false;
+ }
+ return mExistingMap.get(mSelectedAccount).contains(contact);
+ }
+
+ private TreeSet<Long> getEnabledIdsForCurrentAccount() {
+ final TreeSet<Long> result = new TreeSet<>();
+ for (SimContact contact : mContacts) {
+ if (!existsInCurrentAccount(contact)) {
+ result.add(contact.getId());
+ }
+ }
+ return result;
}
private void setViewEnabled(ContactListItemView itemView, boolean enabled) {
itemView.getCheckBox().setEnabled(enabled);
- itemView.getPhotoView().setAlpha(enabled ? 1f : DISABLED_AVATAR_ALPHA);
itemView.getNameTextView().setEnabled(enabled);
}
}
- public static class SimContactLoader extends AsyncTaskLoader<ArrayList<SimContact>> {
+
+ private static class SimContactLoader extends AsyncTaskLoader<LoaderResult> {
private SimContactDao mDao;
private final int mSubscriptionId;
- private ArrayList<SimContact> mData;
+ LoaderResult mResult;
public SimContactLoader(Context context, int subscriptionId) {
super(context);
@@ -354,31 +408,42 @@
@Override
protected void onStartLoading() {
- if (mData != null) {
- deliverResult(mData);
+ if (mResult != null) {
+ deliverResult(mResult);
} else {
forceLoad();
}
}
@Override
- public void deliverResult(ArrayList<SimContact> data) {
- mData = data;
- super.deliverResult(data);
+ public void deliverResult(LoaderResult result) {
+ mResult = result;
+ super.deliverResult(result);
}
@Override
- public ArrayList<SimContact> loadInBackground() {
+ public LoaderResult loadInBackground() {
final SimCard sim = mDao.getSimBySubscriptionId(mSubscriptionId);
+ LoaderResult result = new LoaderResult();
if (sim == null) {
- return new ArrayList<>();
+ result.contacts = new ArrayList<>();
+ result.accountsMap = Collections.emptyMap();
+ return result;
}
- return mDao.loadSimContactsWithExistingContactIds(sim);
+ result.contacts = mDao.loadContactsForSim(sim);
+ result.accountsMap = mDao.findAccountsOfExistingSimContacts(result.contacts);
+ return result;
}
@Override
protected void onReset() {
- mData = null;
+ mResult = null;
}
+
+ }
+
+ public static class LoaderResult {
+ public ArrayList<SimContact> contacts;
+ public Map<AccountWithDataSet, Set<SimContact>> accountsMap;
}
}
diff --git a/src/com/android/contacts/common/Experiments.java b/src/com/android/contacts/common/Experiments.java
index e872694..dee95dd 100644
--- a/src/com/android/contacts/common/Experiments.java
+++ b/src/com/android/contacts/common/Experiments.java
@@ -53,6 +53,12 @@
public static final String DYNAMIC_SHORTCUTS = "Shortcuts__dynamic_shortcuts";
/**
+ * Experiment to enable device account detection using CP2 queries
+ */
+ public static final String OEM_CP2_DEVICE_ACCOUNT_DETECTION_ENABLED =
+ "OEM__cp2_device_account_detection_enabled";
+
+ /**
* Experiment to toggle contacts sync using the pull to refresh gesture.
*/
public static final String PULL_TO_REFRESH = "PullToRefresh__pull_to_refresh";
diff --git a/src/com/android/contacts/common/database/SimContactDao.java b/src/com/android/contacts/common/database/SimContactDao.java
index cab2906..ee36645 100644
--- a/src/com/android/contacts/common/database/SimContactDao.java
+++ b/src/com/android/contacts/common/database/SimContactDao.java
@@ -30,7 +30,10 @@
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
+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;
@@ -50,7 +53,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
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
@@ -149,10 +155,6 @@
return loadFrom(ICC_CONTENT_URI);
}
- public ArrayList<SimContact> loadSimContactsWithExistingContactIds(SimCard sim) {
- return getSimContactsWithRawContacts(sim);
- }
-
public ContentProviderResult[] importContacts(List<SimContact> contacts,
AccountWithDataSet targetAccount)
throws RemoteException, OperationApplicationException {
@@ -193,6 +195,62 @@
return null;
}
+ 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 += IMPORT_MAX_BATCH_SIZE) {
+ findAccountsOfExistingSimContacts(
+ contacts.subList(i, Math.min(contacts.size(), i + IMPORT_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 {
@@ -207,9 +265,6 @@
mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
final List<SubscriptionInfo> subscriptions = subscriptionManager
.getActiveSubscriptionInfoList();
- if (subscriptions == null) {
- return Collections.emptyList();
- }
final ArrayList<SimCard> result = new ArrayList<>();
for (SubscriptionInfo subscriptionInfo : subscriptions) {
result.add(SimCard.create(subscriptionInfo));
@@ -252,19 +307,7 @@
return result;
}
- private ArrayList<SimContact> getSimContactsWithRawContacts(SimCard sim) {
- final ArrayList<SimContact> contacts = new ArrayList<>(getContactsForSim(sim));
- for (int i = 0; i < contacts.size(); i += DataQuery.MAX_BATCH_SIZE) {
- final List<SimContact> batch =
- contacts.subList(i, Math.min(i + DataQuery.MAX_BATCH_SIZE, contacts.size()));
- setRawContactsForSimContacts(batch);
- }
- // Restore default sort order
- Collections.sort(contacts, SimContact.compareById());
- return contacts;
- }
-
- private void setRawContactsForSimContacts(List<SimContact> contacts) {
+ private Cursor queryRawContactsForSimContacts(List<SimContact> contacts) {
final StringBuilder selectionBuilder = new StringBuilder();
int phoneCount = 0;
@@ -287,47 +330,32 @@
}
}
- final Cursor cursor = mResolver.query(ContactsContract.Data.CONTENT_URI.buildUpon()
+ return mResolver.query(ContactsContract.Data.CONTENT_URI.buildUpon()
.appendQueryParameter(ContactsContract.Data.VISIBLE_CONTACTS_ONLY, "true")
.build(),
DataQuery.PROJECTION,
selectionBuilder.toString(),
selectionArgs.toArray(new String[selectionArgs.size()]),
- ContactsContract.Data.RAW_CONTACT_ID + " ASC");
-
- if (cursor == null) {
- initializeRawContactIds(contacts);
- return;
- }
-
- try {
- setRawContactsForSimContacts(contacts, cursor);
- } finally {
- cursor.close();
- }
+ null);
}
- private void initializeRawContactIds(List<SimContact> contacts) {
- for (int i = 0; i < contacts.size(); i++) {
- contacts.set(i, contacts.get(i).withRawContactId(SimContact.NO_EXISTING_CONTACT));
+ 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);
}
- }
-
- private void setRawContactsForSimContacts(List<SimContact> contacts, Cursor cursor) {
- initializeRawContactIds(contacts);
- Collections.sort(contacts, SimContact.compareByPhoneThenName());
-
- while (cursor.moveToNext()) {
- final String number = DataQuery.getPhoneNumber(cursor);
- final String name = DataQuery.getDisplayName(cursor);
-
- int index = SimContact.findByPhoneAndName(contacts, number, name);
- if (index < 0) {
- continue;
- }
- final SimContact contact = contacts.get(index);
- contacts.set(index, contact.withRawContactId(DataQuery.getRawContactId(cursor)));
- }
+ return mResolver.query(RawContacts.CONTENT_URI,
+ AccountQuery.PROJECTION,
+ selectionBuilder.toString(),
+ args,
+ null);
}
private ArrayList<ContentProviderOperation> createImportOperations(List<SimContact> contacts,
@@ -443,4 +471,20 @@
return cursor.getString(DISPLAY_NAME);
}
}
+
+ 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/list/FavoritesAndContactsLoader.java b/src/com/android/contacts/common/list/FavoritesAndContactsLoader.java
index 2dcbc18..e112846 100644
--- a/src/com/android/contacts/common/list/FavoritesAndContactsLoader.java
+++ b/src/com/android/contacts/common/list/FavoritesAndContactsLoader.java
@@ -47,9 +47,6 @@
private CountDownLatch mAutocompleteLatch = new CountDownLatch(1);
private Cursor mAutocompleteCursor;
private int mAutocompleteTimeout;
- // If we didn't get anything back from autocomplete and we've fallen back to CP2,
- // we can't wait for the Experiments.SEARCH_YENTA_TIMEOUT_MILLIS everytime the query changes.
- private boolean mAutocompleteFallback;
public FavoritesAndContactsLoader(Context context) {
super(context);
@@ -74,27 +71,14 @@
@Override
public Cursor loadInBackground() {
List<Cursor> cursors = Lists.newArrayList();
-
- // Load favorites
if (mLoadFavorites) {
cursors.add(loadFavoritesContacts());
}
- // Load contacts
- final Cursor contactsCursor;
- if (mAutocompleteQuery == null || mAutocompleteFallback) {
- // Query CP2 normally
- contactsCursor = loadContacts();
- cursors.add(contactsCursor);
- } else {
+ if (mAutocompleteQuery != null) {
final AutocompleteHelper autocompleteHelper =
ObjectFactory.getAutocompleteHelper(getContext());
- if (autocompleteHelper == null) {
- // Fallback to CP2, the flag is on but we couldn't instantiate autocomplete
- contactsCursor = loadContacts();
- cursors.add(contactsCursor);
- mAutocompleteFallback = true;
- } else {
+ if (autocompleteHelper != null) {
autocompleteHelper.setListener(this);
autocompleteHelper.setProjection(mProjection);
autocompleteHelper.setQuery(mAutocompleteQuery);
@@ -105,18 +89,21 @@
} catch (InterruptedException e) {
logw("Interrupted while waiting for autocompletions");
}
- if (mAutocompleteCursor != null && mAutocompleteCursor.getCount() > 0) {
- contactsCursor = null;
+ if (mAutocompleteCursor != null) {
cursors.add(mAutocompleteCursor);
- } else {
- // Fallback to CP2, we didn't get anything back from autocomplete
- contactsCursor = loadContacts();
- cursors.add(loadContacts());
- mAutocompleteFallback = true;
+ // TODO: exclude these results from the main loader results, see b/30742359
}
}
}
+ // TODO: if the autocomplete experiment in on, only show those results even if they're empty
+ final Cursor contactsCursor = mAutocompleteQuery == null ? loadContacts() : null;
+ if (mAutocompleteQuery == null) {
+ cursors.add(contactsCursor);
+ }
+ // Guard against passing an empty array to the MergeCursor constructor
+ if (cursors.isEmpty()) cursors.add(null);
+
return new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) {
@Override
public Bundle getExtras() {
diff --git a/src/com/android/contacts/common/model/AccountTypeManager.java b/src/com/android/contacts/common/model/AccountTypeManager.java
index 4b0bcc1..15c9771 100644
--- a/src/com/android/contacts/common/model/AccountTypeManager.java
+++ b/src/com/android/contacts/common/model/AccountTypeManager.java
@@ -669,11 +669,10 @@
}
}
- final DeviceLocalAccountLocator deviceAccounts =
- new DeviceLocalAccountLocator(mContext.getContentResolver(),
- mDeviceLocalAccountTypeFactory,
- allAccounts);
- final List<AccountWithDataSet> localAccounts = deviceAccounts.getDeviceLocalAccounts();
+ final DeviceLocalAccountLocator deviceAccountLocator = DeviceLocalAccountLocator
+ .create(mContext, allAccounts);
+ final List<AccountWithDataSet> localAccounts = deviceAccountLocator
+ .getDeviceLocalAccounts();
allAccounts.addAll(localAccounts);
for (AccountWithDataSet localAccount : localAccounts) {
diff --git a/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocator.java b/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocator.java
new file mode 100644
index 0000000..64f7c03
--- /dev/null
+++ b/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocator.java
@@ -0,0 +1,143 @@
+/*
+ * 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.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Attempts to create accounts for "Device" contacts by querying
+ * CP2 for records with {@link android.provider.ContactsContract.RawContacts#ACCOUNT_TYPE} columns
+ * that do not exist for any account returned by {@link AccountManager#getAccounts()}
+ *
+ * This class should be used from a background thread since it does DB queries
+ */
+public class Cp2DeviceLocalAccountLocator extends DeviceLocalAccountLocator {
+
+ // Note this class is assuming ACCOUNT_NAME and ACCOUNT_TYPE have same values in
+ // RawContacts, Groups, and Settings. This assumption simplifies the code somewhat and it
+ // is true right now and unlikely to ever change.
+ @VisibleForTesting
+ static String[] PROJECTION = new String[] {
+ ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE,
+ ContactsContract.RawContacts.DATA_SET
+ };
+
+ private static final int COL_NAME = 0;
+ private static final int COL_TYPE = 1;
+ private static final int COL_DATA_SET = 2;
+
+ private final ContentResolver mResolver;
+ private final DeviceLocalAccountTypeFactory mAccountTypeFactory;
+
+ private final String mSelection;
+ private final String[] mSelectionArgs;
+
+ public Cp2DeviceLocalAccountLocator(ContentResolver contentResolver,
+ DeviceLocalAccountTypeFactory factory,
+ List<AccountWithDataSet> knownAccounts) {
+ mResolver = contentResolver;
+ mAccountTypeFactory = factory;
+
+ final Set<String> knownAccountTypes = new HashSet<>();
+ for (AccountWithDataSet account : knownAccounts) {
+ knownAccountTypes.add(account.type);
+ }
+ mSelection = getSelection(knownAccountTypes);
+ mSelectionArgs = getSelectionArgs(knownAccountTypes);
+ }
+
+ @Override
+ public List<AccountWithDataSet> getDeviceLocalAccounts() {
+
+ final Set<AccountWithDataSet> localAccounts = new HashSet<>();
+
+ // Many device accounts have default groups associated with them.
+ addAccountsFromQuery(ContactsContract.Groups.CONTENT_URI, localAccounts);
+ addAccountsFromQuery(ContactsContract.Settings.CONTENT_URI, localAccounts);
+ addAccountsFromQuery(ContactsContract.RawContacts.CONTENT_URI, localAccounts);
+
+ return new ArrayList<>(localAccounts);
+ }
+
+ private void addAccountsFromQuery(Uri uri, Set<AccountWithDataSet> accounts) {
+ final Cursor cursor = mResolver.query(uri, PROJECTION, mSelection, mSelectionArgs, null);
+
+ if (cursor == null) return;
+
+ try {
+ addAccountsFromCursor(cursor, accounts);
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private void addAccountsFromCursor(Cursor cursor, Set<AccountWithDataSet> accounts) {
+ while (cursor.moveToNext()) {
+ final String name = cursor.getString(COL_NAME);
+ final String type = cursor.getString(COL_TYPE);
+ final String dataSet = cursor.getString(COL_DATA_SET);
+
+ if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
+ mAccountTypeFactory, type)) {
+ accounts.add(new AccountWithDataSet(name, type, dataSet));
+ }
+ }
+ }
+
+ @VisibleForTesting
+ public String getSelection() {
+ return mSelection;
+ }
+
+ @VisibleForTesting
+ public String[] getSelectionArgs() {
+ return mSelectionArgs;
+ }
+
+ private static String getSelection(Set<String> knownAccountTypes) {
+ final StringBuilder sb = new StringBuilder()
+ .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
+ if (knownAccountTypes.isEmpty()) {
+ return sb.toString();
+ }
+ sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
+ for (String ignored : knownAccountTypes) {
+ sb.append("?,");
+ }
+ // Remove trailing ','
+ sb.deleteCharAt(sb.length() - 1).append(')');
+ return sb.toString();
+ }
+
+ private static String[] getSelectionArgs(Set<String> knownAccountTypes) {
+ if (knownAccountTypes.isEmpty()) return null;
+
+ return knownAccountTypes.toArray(new String[knownAccountTypes.size()]);
+ }
+}
diff --git a/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java b/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
index 8c45c40..17f5f5e 100644
--- a/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
+++ b/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
@@ -15,128 +15,41 @@
*/
package com.android.contacts.common.model;
-import android.accounts.AccountManager;
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.support.annotation.VisibleForTesting;
+import android.content.Context;
+import com.android.contacts.common.Experiments;
import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+import com.android.contactsbind.ObjectFactory;
+import com.android.contactsbind.experiments.Flags;
-import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.List;
-import java.util.Set;
/**
- * DeviceLocalAccountLocator attempts to create accounts for "Device" contacts by querying
- * CP2 for records with {@link android.provider.ContactsContract.RawContacts#ACCOUNT_TYPE} columns
- * that do not exist for any account returned by {@link AccountManager#getAccounts()}
- *
- * This class should be used from a background thread since it does DB queries
+ * Attempts to detect accounts for device contacts
*/
-public class DeviceLocalAccountLocator {
+public abstract class DeviceLocalAccountLocator {
- // Note this class is assuming ACCOUNT_NAME and ACCOUNT_TYPE have same values in
- // RawContacts, Groups, and Settings. This assumption simplifies the code somewhat and it
- // is true right now and unlikely to ever change.
- @VisibleForTesting
- static String[] PROJECTION = new String[] {
- ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE,
- ContactsContract.RawContacts.DATA_SET
+ /**
+ * Returns a list of device local accounts
+ */
+ public abstract List<AccountWithDataSet> getDeviceLocalAccounts();
+
+ // This works on Nexus and AOSP because the local device account is the null account but most
+ // OEMs have a special account name and type for their device account.
+ public static final DeviceLocalAccountLocator NULL_ONLY = new DeviceLocalAccountLocator() {
+ @Override
+ public List<AccountWithDataSet> getDeviceLocalAccounts() {
+ return Collections.singletonList(AccountWithDataSet.getNullAccount());
+ }
};
- private static final int COL_NAME = 0;
- private static final int COL_TYPE = 1;
- private static final int COL_DATA_SET = 2;
-
- private final ContentResolver mResolver;
- private final DeviceLocalAccountTypeFactory mAccountTypeFactory;
-
- private final String mSelection;
- private final String[] mSelectionArgs;
-
- public DeviceLocalAccountLocator(ContentResolver contentResolver,
- DeviceLocalAccountTypeFactory factory,
+ public static DeviceLocalAccountLocator create(Context context,
List<AccountWithDataSet> knownAccounts) {
- mResolver = contentResolver;
- mAccountTypeFactory = factory;
-
- final Set<String> knownAccountTypes = new HashSet<>();
- for (AccountWithDataSet account : knownAccounts) {
- knownAccountTypes.add(account.type);
+ if (Flags.getInstance().getBoolean(Experiments.OEM_CP2_DEVICE_ACCOUNT_DETECTION_ENABLED)) {
+ return new Cp2DeviceLocalAccountLocator(context.getContentResolver(),
+ ObjectFactory.getDeviceLocalAccountTypeFactory(context), knownAccounts);
}
- mSelection = getSelection(knownAccountTypes);
- mSelectionArgs = getSelectionArgs(knownAccountTypes);
- }
-
- public List<AccountWithDataSet> getDeviceLocalAccounts() {
-
- final Set<AccountWithDataSet> localAccounts = new HashSet<>();
-
- // Many device accounts have default groups associated with them.
- addAccountsFromQuery(ContactsContract.Groups.CONTENT_URI, localAccounts);
- addAccountsFromQuery(ContactsContract.Settings.CONTENT_URI, localAccounts);
- addAccountsFromQuery(ContactsContract.RawContacts.CONTENT_URI, localAccounts);
-
- return new ArrayList<>(localAccounts);
- }
-
- private void addAccountsFromQuery(Uri uri, Set<AccountWithDataSet> accounts) {
- final Cursor cursor = mResolver.query(uri, PROJECTION, mSelection, mSelectionArgs, null);
-
- if (cursor == null) return;
-
- try {
- addAccountsFromCursor(cursor, accounts);
- } finally {
- cursor.close();
- }
- }
-
- private void addAccountsFromCursor(Cursor cursor, Set<AccountWithDataSet> accounts) {
- while (cursor.moveToNext()) {
- final String name = cursor.getString(COL_NAME);
- final String type = cursor.getString(COL_TYPE);
- final String dataSet = cursor.getString(COL_DATA_SET);
-
- if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
- mAccountTypeFactory, type)) {
- accounts.add(new AccountWithDataSet(name, type, dataSet));
- }
- }
- }
-
- @VisibleForTesting
- public String getSelection() {
- return mSelection;
- }
-
- @VisibleForTesting
- public String[] getSelectionArgs() {
- return mSelectionArgs;
- }
-
- private static String getSelection(Set<String> knownAccountTypes) {
- final StringBuilder sb = new StringBuilder()
- .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
- if (knownAccountTypes.isEmpty()) {
- return sb.toString();
- }
- sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
- for (String ignored : knownAccountTypes) {
- sb.append("?,");
- }
- // Remove trailing ','
- sb.deleteCharAt(sb.length() - 1).append(')');
- return sb.toString();
- }
-
- private static String[] getSelectionArgs(Set<String> knownAccountTypes) {
- if (knownAccountTypes.isEmpty()) return null;
-
- return knownAccountTypes.toArray(new String[knownAccountTypes.size()]);
+ return NULL_ONLY;
}
}
diff --git a/src/com/android/contacts/common/model/SimContact.java b/src/com/android/contacts/common/model/SimContact.java
index 3768503..25af5f8 100644
--- a/src/com/android/contacts/common/model/SimContact.java
+++ b/src/com/android/contacts/common/model/SimContact.java
@@ -40,27 +40,18 @@
* Holds data for contacts loaded from the SIM card.
*/
public class SimContact implements Parcelable {
- public static final long EXISTING_CONTACT_UNINITIALIZED = -2;
- public static final long NO_EXISTING_CONTACT = -1;
-
private final long mId;
private final String mName;
private final String mPhone;
private final String[] mEmails;
- private final long mRawContactId;
-
public SimContact(long id, String name, String phone, String[] emails) {
- this(id, name, phone, emails, EXISTING_CONTACT_UNINITIALIZED);
- }
-
- public SimContact(long id, String name, String phone, String[] emails, long rawContactId) {
mId = id;
mName = name;
mPhone = phone;
mEmails = emails;
- mRawContactId = rawContactId;
}
+
public long getId() {
return mId;
}
@@ -77,13 +68,6 @@
return mEmails;
}
- public boolean existsInContacts() {
- if (mRawContactId == EXISTING_CONTACT_UNINITIALIZED) {
- throw new IllegalStateException("Raw contact ID is uninitialized");
- }
- return mRawContactId > 0;
- }
-
public void appendCreateContactOperations(List<ContentProviderOperation> ops,
AccountWithDataSet targetAccount) {
// nothing to save.
@@ -139,10 +123,6 @@
return mEmails != null && mEmails.length > 0;
}
- public SimContact withRawContactId(long id) {
- return new SimContact(mId, mName, mPhone, mEmails, id);
- }
-
/**
* Generate a "fake" lookup key. This is needed because
* {@link com.android.contacts.common.ContactPhotoManager} will only generate a letter avatar
@@ -165,7 +145,6 @@
", mName='" + mName + '\'' +
", mPhone='" + mPhone + '\'' +
", mEmails=" + Arrays.toString(mEmails) +
- ", mRawContactId=" + mRawContactId +
'}';
}
@@ -176,9 +155,8 @@
final SimContact that = (SimContact) o;
- return mId == that.mId && mRawContactId == that.mRawContactId
- && Objects.equals(mName, that.mName) && Objects.equals(mPhone, that.mPhone)
- && Arrays.equals(mEmails, that.mEmails);
+ return mId == that.mId && Objects.equals(mName, that.mName) &&
+ Objects.equals(mPhone, that.mPhone) && Arrays.equals(mEmails, that.mEmails);
}
@Override
@@ -187,7 +165,6 @@
result = 31 * result + (mName != null ? mName.hashCode() : 0);
result = 31 * result + (mPhone != null ? mPhone.hashCode() : 0);
result = 31 * result + Arrays.hashCode(mEmails);
- result = 31 * result + (int) (mRawContactId ^ (mRawContactId >>> 32));
return result;
}
@@ -202,9 +179,24 @@
dest.writeString(mName);
dest.writeString(mPhone);
dest.writeStringArray(mEmails);
- dest.writeLong(mRawContactId);
}
+ public static final Creator<SimContact> CREATOR = new Creator<SimContact>() {
+ @Override
+ public SimContact createFromParcel(Parcel source) {
+ final long id = source.readLong();
+ final String name = source.readString();
+ final String phone = source.readString();
+ final String[] emails = source.createStringArray();
+ return new SimContact(id, name, phone, emails);
+ }
+
+ @Override
+ public SimContact[] newArray(int size) {
+ return new SimContact[size];
+ }
+ };
+
/**
* Convert a collection of SIM contacts to a Cursor matching a query from
* {@link android.provider.ContactsContract.Contacts#CONTENT_URI} with the provided projection.
@@ -221,23 +213,6 @@
return result;
}
- public static final Creator<SimContact> CREATOR = new Creator<SimContact>() {
- @Override
- public SimContact createFromParcel(Parcel source) {
- final long id = source.readLong();
- final String name = source.readString();
- final String phone = source.readString();
- final String[] emails = source.createStringArray();
- final long contactId = source.readLong();
- return new SimContact(id, name, phone, emails, contactId);
- }
-
- @Override
- public SimContact[] newArray(int size) {
- return new SimContact[size];
- }
- };
-
/**
* Returns the index of a contact with a matching name and phone
* @param contacts list to search. Should be sorted using
@@ -246,8 +221,8 @@
* @param name the name to search for
*/
public static int findByPhoneAndName(List<SimContact> contacts, String phone, String name) {
- return Collections.binarySearch(contacts, new SimContact(-1, name, phone, null,
- NO_EXISTING_CONTACT), compareByPhoneThenName());
+ return Collections.binarySearch(contacts, new SimContact(-1, name, phone, null),
+ compareByPhoneThenName());
}
public static final Comparator<SimContact> compareByPhoneThenName() {
diff --git a/src/com/android/contacts/common/model/account/AccountWithDataSet.java b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
index 37f6652..3ee0aab 100644
--- a/src/com/android/contacts/common/model/account/AccountWithDataSet.java
+++ b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
@@ -243,9 +243,13 @@
public static AccountWithDataSet getDefaultOrBestFallback(ContactsPreferences preferences,
AccountTypeManager accountTypeManager) {
if (preferences.isDefaultAccountSet()) {
- return preferences.getDefaultAccount();
+ final AccountWithDataSet account = preferences.getDefaultAccount();
+ if (accountTypeManager.contains(account, true)) {
+ return account;
+ }
}
- List<AccountWithDataSet> accounts = accountTypeManager.getAccounts(/* writableOnly */ true);
+ final List<AccountWithDataSet> accounts = accountTypeManager
+ .getAccounts(/* writableOnly */ true);
if (accounts.isEmpty()) {
return AccountWithDataSet.getNullAccount();
diff --git a/tests/src/com/android/contacts/common/database/SimContactDaoTests.java b/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
index d0f8990..7250593 100644
--- a/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
+++ b/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
@@ -1,358 +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.filters.Suppress;
-//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;
-//
-//@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();
-// }
-//}
+/*
+ * 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.filters.Suppress;
+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;
+
+@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 = SimContactDao.create(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 = SimContactDao.create(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 = SimContactDao.create(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 = SimContactDao.create(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 = SimContactDao.create(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/DeviceLocalAccountLocatorTests.java b/tests/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocatorTests.java
similarity index 94%
rename from tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
rename to tests/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocatorTests.java
index e8c4e2f..192eb44 100644
--- a/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
+++ b/tests/src/com/android/contacts/common/model/Cp2DeviceLocalAccountLocatorTests.java
@@ -40,12 +40,12 @@
import java.util.Map;
@SmallTest
-public class DeviceLocalAccountLocatorTests extends AndroidTestCase {
+public class Cp2DeviceLocalAccountLocatorTests extends AndroidTestCase {
// Basic smoke test that just checks that it doesn't throw when loading from CP2. We don't
// care what CP2 actually contains for this.
public void testShouldNotCrash() {
- final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+ final DeviceLocalAccountLocator sut = new Cp2DeviceLocalAccountLocator(
getContext().getContentResolver(),
new DeviceLocalAccountTypeFactory.Default(getContext()),
Collections.<AccountWithDataSet>emptyList());
@@ -80,7 +80,7 @@
final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory()
.withDeviceTypes(null, "vnd.sec.contact.phone")
.withSimTypes("vnd.sec.contact.sim");
- final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+ final DeviceLocalAccountLocator sut = new Cp2DeviceLocalAccountLocator(
createStubResolverWithContentQueryResult(queryResult(
"user", "com.example",
"user", "com.example",
@@ -98,7 +98,7 @@
}
public void test_getDeviceLocalAccounts_doesNotContainItemsForKnownAccounts() {
- final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+ final Cp2DeviceLocalAccountLocator sut = new Cp2DeviceLocalAccountLocator(
getContext().getContentResolver(), new FakeDeviceAccountTypeFactory(),
Arrays.asList(new AccountWithDataSet("user", "com.example", null),
new AccountWithDataSet("user1", "com.example", null),
@@ -116,7 +116,7 @@
final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory()
.withDeviceTypes(null, "vnd.sec.contact.phone")
.withSimTypes("vnd.sec.contact.sim");
- final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+ final DeviceLocalAccountLocator sut = new Cp2DeviceLocalAccountLocator(
createContentResolverWithProvider(new FakeContactsProvider()
.withQueryResult(ContactsContract.Settings.CONTENT_URI, queryResult(
"phone_account", "vnd.sec.contact.phone",
@@ -130,7 +130,7 @@
final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory()
.withDeviceTypes(null, "vnd.sec.contact.phone")
.withSimTypes("vnd.sec.contact.sim");
- final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+ final DeviceLocalAccountLocator sut = new Cp2DeviceLocalAccountLocator(
createContentResolverWithProvider(new FakeContactsProvider()
.withQueryResult(ContactsContract.Groups.CONTENT_URI, queryResult(
"phone_account", "vnd.sec.contact.phone",
@@ -142,7 +142,7 @@
private DeviceLocalAccountLocator createWithQueryResult(
Cursor cursor) {
- final DeviceLocalAccountLocator locator = new DeviceLocalAccountLocator(
+ final DeviceLocalAccountLocator locator = new Cp2DeviceLocalAccountLocator(
createStubResolverWithContentQueryResult(cursor),
new DeviceLocalAccountTypeFactory.Default(getContext()),
Collections.<AccountWithDataSet>emptyList());
diff --git a/tests/src/com/android/contacts/common/model/SimContactTests.java b/tests/src/com/android/contacts/common/model/SimContactTests.java
index c37c270..359379e 100644
--- a/tests/src/com/android/contacts/common/model/SimContactTests.java
+++ b/tests/src/com/android/contacts/common/model/SimContactTests.java
@@ -1,3 +1,18 @@
+/*
+ * 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.Parcel;
@@ -10,10 +25,6 @@
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 {
@@ -21,7 +32,7 @@
public void parcelRoundtrip() {
assertParcelsCorrectly(new SimContact(1, "name1", "phone1",
new String[] { "email1a", "email1b" }));
- assertParcelsCorrectly(new SimContact(2, "name2", "phone2", null, 2));
+ assertParcelsCorrectly(new SimContact(2, "name2", "phone2", null));
assertParcelsCorrectly(new SimContact(3, "name3", null,
new String[] { "email3" }));
assertParcelsCorrectly(new SimContact(4, null, "phone4",
diff --git a/tests/src/com/android/contacts/tests/SimContactsTestHelper.java b/tests/src/com/android/contacts/tests/SimContactsTestHelper.java
index 337ea62..c2ffead 100644
--- a/tests/src/com/android/contacts/tests/SimContactsTestHelper.java
+++ b/tests/src/com/android/contacts/tests/SimContactsTestHelper.java
@@ -1,198 +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());
-// }
-//}
+/*
+ * 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 = SimContactDao.create(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 = SimContactDao.create(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());
+ }
+}