diff options
author | Brian Attwell <brianattwell@google.com> | 2015-03-03 11:13:49 -0800 |
---|---|---|
committer | Brian Attwell <brianattwell@google.com> | 2015-03-03 15:51:26 -0800 |
commit | d3946cae17273ed1c2fceb507990882e3f828ba9 (patch) | |
tree | c3877cb532d6905963c077e7a07d0ba820ac614a /src | |
parent | d2962a3bb669a381d31a586df3b906033a8fa571 (diff) | |
download | packages_apps_Contacts-d3946cae17273ed1c2fceb507990882e3f828ba9.tar.gz packages_apps_Contacts-d3946cae17273ed1c2fceb507990882e3f828ba9.tar.bz2 packages_apps_Contacts-d3946cae17273ed1c2fceb507990882e3f828ba9.zip |
Batch join contacts
* Add new action to ContactSaveService to support joining more than
two contacts toghether.
* Add new dialog fragment for the join
Bug: 19549465
Change-Id: Ib0b1d5e7652e429f8e78d81dd3d98d03b3129e1e
Diffstat (limited to 'src')
6 files changed, 214 insertions, 31 deletions
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java index 166852188..cf36edf9a 100644 --- a/src/com/android/contacts/ContactSaveService.java +++ b/src/com/android/contacts/ContactSaveService.java @@ -109,9 +109,9 @@ public class ContactSaveService extends IntentService { public static final String EXTRA_DATA_ID = "dataId"; public static final String ACTION_JOIN_CONTACTS = "joinContacts"; + public static final String ACTION_JOIN_SEVERAL_CONTACTS = "joinSeveralContacts"; public static final String EXTRA_CONTACT_ID1 = "contactId1"; public static final String EXTRA_CONTACT_ID2 = "contactId2"; - public static final String EXTRA_CONTACT_WRITABLE = "contactWritable"; public static final String ACTION_SET_SEND_TO_VOICEMAIL = "sendToVoicemail"; public static final String EXTRA_SEND_TO_VOICEMAIL_FLAG = "sendToVoicemailFlag"; @@ -211,6 +211,8 @@ public class ContactSaveService extends IntentService { deleteContact(intent); } else if (ACTION_JOIN_CONTACTS.equals(action)) { joinContacts(intent); + } else if (ACTION_JOIN_SEVERAL_CONTACTS.equals(action)) { + joinSeveralContacts(intent); } else if (ACTION_SET_SEND_TO_VOICEMAIL.equals(action)) { setSendToVoicemail(intent); } else if (ACTION_SET_RINGTONE.equals(action)) { @@ -985,15 +987,14 @@ public class ContactSaveService extends IntentService { /** * Creates an intent that can be sent to this service to join two contacts. + * The resulting contact uses the name from {@param contactId1} if possible. */ public static Intent createJoinContactsIntent(Context context, long contactId1, - long contactId2, boolean contactWritable, - Class<? extends Activity> callbackActivity, String callbackAction) { + long contactId2, Class<? extends Activity> callbackActivity, String callbackAction) { Intent serviceIntent = new Intent(context, ContactSaveService.class); serviceIntent.setAction(ContactSaveService.ACTION_JOIN_CONTACTS); serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID1, contactId1); serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_ID2, contactId2); - serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_WRITABLE, contactWritable); // Callback intent will be invoked by the service once the contacts are joined. Intent callbackIntent = new Intent(context, callbackActivity); @@ -1003,6 +1004,17 @@ public class ContactSaveService extends IntentService { return serviceIntent; } + /** + * Creates an intent to join all raw contacts inside {@param contactIds}'s contacts. + * No special attention is paid to where the resulting contact's name is taken from. + */ + public static Intent createJoinSeveralContactsIntent(Context context, long[] contactIds) { + Intent serviceIntent = new Intent(context, ContactSaveService.class); + serviceIntent.setAction(ContactSaveService.ACTION_JOIN_SEVERAL_CONTACTS); + serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds); + return serviceIntent; + } + private interface JoinContactQuery { String[] PROJECTION = { @@ -1011,8 +1023,6 @@ public class ContactSaveService extends IntentService { RawContacts.DISPLAY_NAME_SOURCE, }; - String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?"; - int _ID = 0; int CONTACT_ID = 1; int DISPLAY_NAME_SOURCE = 2; @@ -1034,22 +1044,48 @@ public class ContactSaveService extends IntentService { int IS_SUPER_PRIMARY = 2; } - private void joinContacts(Intent intent) { - long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1); - long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1); + private void joinSeveralContacts(Intent intent) { + final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS); - if (contactId1 == -1 || contactId2 == -1) { - Log.e(TAG, "Invalid arguments for joinContacts request"); + // Load raw contact IDs for all contacts involved. + long rawContactIds[] = getRawContactIdsForAggregation(contactIds); + if (rawContactIds == null) { + Log.e(TAG, "Invalid arguments for joinSeveralContacts request"); return; } + // For each pair of raw contacts, insert an aggregation exception final ContentResolver resolver = getContentResolver(); + final ArrayList<ContentProviderOperation> operations + = new ArrayList<ContentProviderOperation>(); + for (int i = 0; i < rawContactIds.length; i++) { + for (int j = 0; j < rawContactIds.length; j++) { + if (i != j) { + buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]); + } + } + } + + // Apply all aggregation exceptions as one batch + try { + resolver.applyBatch(ContactsContract.AUTHORITY, operations); + showToast(R.string.contactsJoinedMessage); + } catch (RemoteException | OperationApplicationException e) { + Log.e(TAG, "Failed to apply aggregation exception batch", e); + showToast(R.string.contactSavedErrorToast); + } + } + + + private void joinContacts(Intent intent) { + long contactId1 = intent.getLongExtra(EXTRA_CONTACT_ID1, -1); + long contactId2 = intent.getLongExtra(EXTRA_CONTACT_ID2, -1); // Load raw contact IDs for all raw contacts involved - currently edited and selected // in the join UIs. long rawContactIds[] = getRawContactIdsForAggregation(contactId1, contactId2); if (rawContactIds == null) { - // Error. + Log.e(TAG, "Invalid arguments for joinContacts request"); return; } @@ -1064,6 +1100,8 @@ public class ContactSaveService extends IntentService { } } + final ContentResolver resolver = getContentResolver(); + // Use the name for contactId1 as the name for the newly aggregated contact. final Uri contactId1Uri = ContentUris.withAppendedId( Contacts.CONTENT_URI, contactId1); @@ -1101,10 +1139,7 @@ public class ContactSaveService extends IntentService { resolver.applyBatch(ContactsContract.AUTHORITY, operations); showToast(R.string.contactsJoinedMessage); success = true; - } catch (RemoteException e) { - Log.e(TAG, "Failed to apply aggregation exception batch", e); - showToast(R.string.contactSavedErrorToast); - } catch (OperationApplicationException e) { + } catch (RemoteException | OperationApplicationException e) { Log.e(TAG, "Failed to apply aggregation exception batch", e); showToast(R.string.contactSavedErrorToast); } @@ -1118,13 +1153,32 @@ public class ContactSaveService extends IntentService { deliverCallback(callbackIntent); } - private long[] getRawContactIdsForAggregation(long contactId1, long contactId2) { + private long[] getRawContactIdsForAggregation(long[] contactIds) { + if (contactIds == null) { + return null; + } + final ContentResolver resolver = getContentResolver(); long rawContactIds[]; + + final StringBuilder queryBuilder = new StringBuilder(); + final String stringContactIds[] = new String[contactIds.length]; + for (int i = 0; i < contactIds.length; i++) { + queryBuilder.append(RawContacts.CONTACT_ID + "=?"); + stringContactIds[i] = String.valueOf(contactIds[i]); + if (contactIds[i] == -1) { + return null; + } + if (i == contactIds.length -1) { + break; + } + queryBuilder.append(" OR "); + } + final Cursor c = resolver.query(RawContacts.CONTENT_URI, JoinContactQuery.PROJECTION, - JoinContactQuery.SELECTION, - new String[]{String.valueOf(contactId1), String.valueOf(contactId2)}, null); + queryBuilder.toString(), + stringContactIds, null); if (c == null) { Log.e(TAG, "Unable to open Contacts DB cursor"); showToast(R.string.contactSavedErrorToast); @@ -1132,7 +1186,7 @@ public class ContactSaveService extends IntentService { } try { if (c.getCount() < 2) { - Log.e(TAG, "Not enough raw contacts to aggregate toghether."); + Log.e(TAG, "Not enough raw contacts to aggregate together."); return null; } rawContactIds = new long[c.getCount()]; @@ -1147,6 +1201,10 @@ public class ContactSaveService extends IntentService { return rawContactIds; } + private long[] getRawContactIdsForAggregation(long contactId1, long contactId2) { + return getRawContactIdsForAggregation(new long[] {contactId1, contactId2}); + } + /** * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation. */ diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java index 577bf57f1..d660f4fa3 100644 --- a/src/com/android/contacts/activities/PeopleActivity.java +++ b/src/com/android/contacts/activities/PeopleActivity.java @@ -64,6 +64,8 @@ import com.android.contacts.common.list.ContactListFilterController; import com.android.contacts.common.list.ContactTileAdapter.DisplayType; import com.android.contacts.interactions.ContactMultiDeletionInteraction; import com.android.contacts.interactions.ContactMultiDeletionInteraction.MultiContactDeleteListener; +import com.android.contacts.interactions.JoinContactsDialogFragment; +import com.android.contacts.interactions.JoinContactsDialogFragment.JoinContactsListener; import com.android.contacts.list.MultiSelectContactsListFragment; import com.android.contacts.list.MultiSelectContactsListFragment.OnCheckBoxListActionListener; import com.android.contacts.list.ContactTileListFragment; @@ -100,7 +102,8 @@ public class PeopleActivity extends ContactsActivity implements DialogManager.DialogShowingViewActivity, ContactListFilterController.ContactListFilterListener, ProviderStatusListener, - MultiContactDeleteListener { + MultiContactDeleteListener, + JoinContactsListener { private static final String TAG = "PeopleActivity"; @@ -1065,6 +1068,8 @@ public class PeopleActivity extends ContactsActivity implements && mAllFragment.getSelectedContactIds().size() != 0; makeMenuItemVisible(menu, R.id.menu_share, showSelectedContactOptions); makeMenuItemVisible(menu, R.id.menu_delete, showSelectedContactOptions); + makeMenuItemVisible(menu, R.id.menu_join, showSelectedContactOptions); + makeMenuItemEnabled(menu, R.id.menu_join, mAllFragment.getSelectedContactIds().size() > 1); // Debug options need to be visible even in search mode. makeMenuItemVisible(menu, R.id.export_database, mEnableDebugMenuOptions); @@ -1081,12 +1086,19 @@ public class PeopleActivity extends ContactsActivity implements } private void makeMenuItemVisible(Menu menu, int itemId, boolean visible) { - MenuItem item =menu.findItem(itemId); + final MenuItem item = menu.findItem(itemId); if (item != null) { item.setVisible(visible); } } + private void makeMenuItemEnabled(Menu menu, int itemId, boolean visible) { + final MenuItem item = menu.findItem(itemId); + if (item != null) { + item.setEnabled(visible); + } + } + @Override public boolean onOptionsItemSelected(MenuItem item) { if (mDisableOptionItemSelected) { @@ -1130,6 +1142,9 @@ public class PeopleActivity extends ContactsActivity implements case R.id.menu_share: shareSelectedContacts(); return true; + case R.id.menu_join: + joinSelectedContacts(); + return true; case R.id.menu_delete: deleteSelectedContacts(); return true; @@ -1197,6 +1212,15 @@ public class PeopleActivity extends ContactsActivity implements ImplicitIntentsUtil.startActivityOutsideApp(this, intent); } + private void joinSelectedContacts() { + JoinContactsDialogFragment.start(this, mAllFragment.getSelectedContactIds()); + } + + @Override + public void onContactsJoined() { + mAllFragment.clearCheckBoxes(); + } + private void deleteSelectedContacts() { ContactMultiDeletionInteraction.start(PeopleActivity.this, mAllFragment.getSelectedContactIds()); diff --git a/src/com/android/contacts/editor/CompactContactEditorFragment.java b/src/com/android/contacts/editor/CompactContactEditorFragment.java index f25d01aff..94e22632e 100644 --- a/src/com/android/contacts/editor/CompactContactEditorFragment.java +++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java @@ -304,8 +304,7 @@ public class CompactContactEditorFragment extends ContactEditorBaseFragment impl @Override protected void joinAggregate(final long contactId) { final Intent intent = ContactSaveService.createJoinContactsIntent( - mContext, mContactIdForJoin, contactId, mContactWritableForJoin, - CompactContactEditorActivity.class, + mContext, mContactIdForJoin, contactId, CompactContactEditorActivity.class, CompactContactEditorActivity.ACTION_JOIN_COMPLETED); mContext.startService(intent); } diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java index 57905d129..d8c20fb95 100644 --- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java +++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java @@ -139,7 +139,6 @@ abstract public class ContactEditorBaseFragment extends Fragment implements // Join Activity private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin"; - private static final String KEY_CONTACT_WRITABLE_FOR_JOIN = "contactwritableforjoin"; protected static final int REQUEST_CODE_JOIN = 0; protected static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1; @@ -332,7 +331,6 @@ abstract public class ContactEditorBaseFragment extends Fragment implements // Join Activity protected long mContactIdForJoin; - protected boolean mContactWritableForJoin; // // Editor state for {@link ContactEditorView}. @@ -465,7 +463,6 @@ abstract public class ContactEditorBaseFragment extends Fragment implements // Join Activity mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN); - mContactWritableForJoin = savedState.getBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN); } // mState can still be null because it may not have have finished loading before @@ -582,7 +579,6 @@ abstract public class ContactEditorBaseFragment extends Fragment implements // Join Activity outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin); - outState.putBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN, mContactWritableForJoin); super.onSaveInstanceState(outState); } @@ -1337,7 +1333,6 @@ abstract public class ContactEditorBaseFragment extends Fragment implements } mContactIdForJoin = ContentUris.parseId(contactLookupUri); - mContactWritableForJoin = isContactWritable(); final Intent intent = new Intent(UiIntentActions.PICK_JOIN_CONTACT_ACTION); intent.putExtra(UiIntentActions.TARGET_CONTACT_ID_EXTRA_KEY, mContactIdForJoin); startActivityForResult(intent, REQUEST_CODE_JOIN); diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java index 96aaecde1..9e5148d48 100644 --- a/src/com/android/contacts/editor/ContactEditorFragment.java +++ b/src/com/android/contacts/editor/ContactEditorFragment.java @@ -509,8 +509,8 @@ public class ContactEditorFragment extends ContactEditorBaseFragment implements @Override protected void joinAggregate(final long contactId) { final Intent intent = ContactSaveService.createJoinContactsIntent( - mContext, mContactIdForJoin, contactId, mContactWritableForJoin, - ContactEditorActivity.class, ContactEditorActivity.ACTION_JOIN_COMPLETED); + mContext, mContactIdForJoin, contactId, ContactEditorActivity.class, + ContactEditorActivity.ACTION_JOIN_COMPLETED); mContext.startService(intent); } diff --git a/src/com/android/contacts/interactions/JoinContactsDialogFragment.java b/src/com/android/contacts/interactions/JoinContactsDialogFragment.java new file mode 100644 index 000000000..a9a1aa976 --- /dev/null +++ b/src/com/android/contacts/interactions/JoinContactsDialogFragment.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015 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.interactions; + + +import com.android.contacts.ContactSaveService; +import com.android.contacts.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentTransaction; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; + +import java.util.TreeSet; + +/** + * An interaction invoked to join multiple contacts together. + */ +public class JoinContactsDialogFragment extends DialogFragment { + + private static final String FRAGMENT_TAG = "joinDialog"; + private static final String KEY_CONTACT_IDS = "contactIds"; + + public interface JoinContactsListener { + void onContactsJoined(); + } + + public static void start(Activity activity, TreeSet<Long> contactIds) { + final FragmentTransaction ft = activity.getFragmentManager().beginTransaction(); + final JoinContactsDialogFragment newFragment + = JoinContactsDialogFragment.newInstance(contactIds); + newFragment.show(ft, FRAGMENT_TAG); + } + + private static JoinContactsDialogFragment newInstance(TreeSet<Long> contactIds) { + final JoinContactsDialogFragment fragment = new JoinContactsDialogFragment(); + Bundle arguments = new Bundle(); + arguments.putSerializable(KEY_CONTACT_IDS, contactIds); + fragment.setArguments(arguments); + return fragment; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final TreeSet<Long> contactIds = + (TreeSet<Long>) getArguments().getSerializable(KEY_CONTACT_IDS); + if (contactIds.size() <= 1) { + return new AlertDialog.Builder(getActivity()) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setMessage(R.string.batch_merge_single_contact_warning) + .setPositiveButton(android.R.string.ok, null) + .create(); + } + return new AlertDialog.Builder(getActivity()) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setMessage(R.string.batch_merge_confirmation) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + joinContacts(contactIds); + } + } + ) + .create(); + } + + private void joinContacts(TreeSet<Long> contactIds) { + final Long[] contactIdsArray = contactIds.toArray(new Long[contactIds.size()]); + final long[] contactIdsArray2 = new long[contactIdsArray.length]; + for (int i = 0; i < contactIds.size(); i++) { + contactIdsArray2[i] = contactIdsArray[i]; + } + + final Intent intent = ContactSaveService.createJoinSeveralContactsIntent(getActivity(), + contactIdsArray2); + getActivity().startService(intent); + + notifyListener(); + } + + private void notifyListener() { + if (getActivity() instanceof JoinContactsListener) { + ((JoinContactsListener) getActivity()).onContactsJoined(); + } + } + +} |