Disable only for current account in SIM import
SIM contacts are now only disabled if they exist in CP2 in the account that
is current selected in the SimImportFragment.
Test
manual:
1. have device with multiple accounts
2. import contacts from SIM card
3. go back to SIM import screen
4. verify that contacts are disabled and unselected when account from step 2
is selected
5. verify that contacts are enabled and selected when a different account is
selected
Bug 31781331
Bug 32575794
Change-Id: I682c678bfccf78abfcc67cb354efe7bd92fb3676
diff --git a/src/com/android/contacts/SimImportFragment.java b/src/com/android/contacts/SimImportFragment.java
index bc20d62..1f8436c 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";
@@ -127,13 +130,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 +188,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 +199,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 +215,7 @@
}
@Override
- public void onLoaderReset(Loader<ArrayList<SimContact>> loader) {
+ public void onLoaderReset(Loader<LoaderResult> loader) {
}
private void importCurrentSelections() {
@@ -268,6 +278,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);
@@ -288,15 +301,39 @@
// 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,8 +367,25 @@
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) {
@@ -341,10 +395,11 @@
}
}
- 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 +409,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/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/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/tests/src/com/android/contacts/common/model/SimContactTests.java b/tests/src/com/android/contacts/common/model/SimContactTests.java
index c37c270..de9ab5a 100644
--- a/tests/src/com/android/contacts/common/model/SimContactTests.java
+++ b/tests/src/com/android/contacts/common/model/SimContactTests.java
@@ -21,7 +21,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",