Batch deletion
Also: when re-opening the Activity from the launcher, clear the
mIsInSelectionMode variable in memory.
Bug: 19549465
Change-Id: If589983d3d84c9c18066da08f9879c22db1a75ed
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index c43941f..1668521 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -99,7 +99,9 @@
public static final String ACTION_SET_STARRED = "setStarred";
public static final String ACTION_DELETE_CONTACT = "delete";
+ public static final String ACTION_DELETE_MULTIPLE_CONTACTS = "deleteMultipleContacts";
public static final String EXTRA_CONTACT_URI = "contactUri";
+ public static final String EXTRA_CONTACT_IDS = "contactIds";
public static final String EXTRA_STARRED_FLAG = "starred";
public static final String ACTION_SET_SUPER_PRIMARY = "setSuperPrimary";
@@ -203,6 +205,8 @@
setSuperPrimary(intent);
} else if (ACTION_CLEAR_PRIMARY.equals(action)) {
clearPrimary(intent);
+ } else if (ACTION_DELETE_MULTIPLE_CONTACTS.equals(action)) {
+ deleteMultipleContacts(intent);
} else if (ACTION_DELETE_CONTACT.equals(action)) {
deleteContact(intent);
} else if (ACTION_JOIN_CONTACTS.equals(action)) {
@@ -945,6 +949,17 @@
return serviceIntent;
}
+ /**
+ * Creates an intent that can be sent to this service to delete multiple contacts.
+ */
+ public static Intent createDeleteMultipleContactsIntent(Context context,
+ long[] contactIds) {
+ Intent serviceIntent = new Intent(context, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_DELETE_MULTIPLE_CONTACTS);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_CONTACT_IDS, contactIds);
+ return serviceIntent;
+ }
+
private void deleteContact(Intent intent) {
Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI);
if (contactUri == null) {
@@ -955,6 +970,19 @@
getContentResolver().delete(contactUri, null, null);
}
+ private void deleteMultipleContacts(Intent intent) {
+ final long[] contactIds = intent.getLongArrayExtra(EXTRA_CONTACT_IDS);
+ if (contactIds == null) {
+ Log.e(TAG, "Invalid arguments for deleteMultipleContacts request");
+ return;
+ }
+ for (long contactId : contactIds) {
+ final Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ getContentResolver().delete(contactUri, null, null);
+ }
+
+ }
+
/**
* Creates an intent that can be sent to this service to join two contacts.
*/
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index f6cb921..dc9fcf2 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -192,6 +192,7 @@
mSearchMode = request.isSearchMode();
mQueryString = request.getQueryString();
mCurrentTab = loadLastTabPreference();
+ mSelectionMode = false;
} else {
mSearchMode = savedState.getBoolean(EXTRA_KEY_SEARCH_MODE);
mSelectionMode = savedState.getBoolean(EXTRA_KEY_SELECTED_MODE);
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index a3af101..577bf57 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -62,6 +62,8 @@
import com.android.contacts.common.list.ContactListFilter;
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.list.MultiSelectContactsListFragment;
import com.android.contacts.list.MultiSelectContactsListFragment.OnCheckBoxListActionListener;
import com.android.contacts.list.ContactTileListFragment;
@@ -97,7 +99,8 @@
ActionBarAdapter.Listener,
DialogManager.DialogShowingViewActivity,
ContactListFilterController.ContactListFilterListener,
- ProviderStatusListener {
+ ProviderStatusListener,
+ MultiContactDeleteListener {
private static final String TAG = "PeopleActivity";
@@ -569,6 +572,9 @@
mTabPager.setCurrentItem(tab, !wereTabsHidden);
}
}
+ if (!mActionBarAdapter.isSelectionMode()) {
+ mAllFragment.displayCheckBoxes(false);
+ }
invalidateOptionsMenu();
showEmptyStateForTab(tab);
}
@@ -1124,6 +1130,9 @@
case R.id.menu_share:
shareSelectedContacts();
return true;
+ case R.id.menu_delete:
+ deleteSelectedContacts();
+ return true;
case R.id.menu_import_export: {
ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(),
PeopleActivity.class);
@@ -1188,6 +1197,16 @@
ImplicitIntentsUtil.startActivityOutsideApp(this, intent);
}
+ private void deleteSelectedContacts() {
+ ContactMultiDeletionInteraction.start(PeopleActivity.this,
+ mAllFragment.getSelectedContactIds());
+ }
+
+ @Override
+ public void onDeletionFinished() {
+ mAllFragment.clearCheckBoxes();
+ }
+
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
diff --git a/src/com/android/contacts/interactions/ContactMultiDeletionInteraction.java b/src/com/android/contacts/interactions/ContactMultiDeletionInteraction.java
new file mode 100644
index 0000000..7c13178
--- /dev/null
+++ b/src/com/android/contacts/interactions/ContactMultiDeletionInteraction.java
@@ -0,0 +1,297 @@
+/*
+ * 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.google.common.collect.Sets;
+
+import com.android.contacts.ContactSaveService;
+import com.android.contacts.R;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountType;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.TreeSet;
+
+/**
+ * An interaction invoked to delete multiple contacts.
+ *
+ * This class is very similar to {@link ContactDeletionInteraction}.
+ */
+public class ContactMultiDeletionInteraction extends Fragment
+ implements LoaderCallbacks<Cursor> {
+
+ public interface MultiContactDeleteListener {
+ void onDeletionFinished();
+ }
+
+ private static final String FRAGMENT_TAG = "deleteMultipleContacts";
+ private static final String TAG = "ContactMultiDeletionInteraction";
+ private static final String KEY_ACTIVE = "active";
+ private static final String KEY_CONTACTS_IDS = "contactIds";
+ public static final String ARG_CONTACT_IDS = "contactIds";
+
+ private static final String[] RAW_CONTACT_PROJECTION = new String[] {
+ RawContacts._ID,
+ RawContacts.ACCOUNT_TYPE,
+ RawContacts.DATA_SET,
+ RawContacts.CONTACT_ID,
+ };
+
+ private static final int COLUMN_INDEX_RAW_CONTACT_ID = 0;
+ private static final int COLUMN_INDEX_ACCOUNT_TYPE = 1;
+ private static final int COLUMN_INDEX_DATA_SET = 2;
+ private static final int COLUMN_INDEX_CONTACT_ID = 3;
+
+ private boolean mIsLoaderActive;
+ private TreeSet<Long> mContactIds;
+ private Context mContext;
+ private AlertDialog mDialog;
+
+ /**
+ * Starts the interaction.
+ *
+ * @param activity the activity within which to start the interaction
+ * @param contactIds the IDs of contacts to be deleted
+ * @return the newly created interaction
+ */
+ public static ContactMultiDeletionInteraction start(
+ Activity activity, TreeSet<Long> contactIds) {
+ if (contactIds == null) {
+ return null;
+ }
+
+ final FragmentManager fragmentManager = activity.getFragmentManager();
+ ContactMultiDeletionInteraction fragment =
+ (ContactMultiDeletionInteraction) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
+ if (fragment == null) {
+ fragment = new ContactMultiDeletionInteraction();
+ fragment.setContactIds(contactIds);
+ fragmentManager.beginTransaction().add(fragment, FRAGMENT_TAG)
+ .commitAllowingStateLoss();
+ } else {
+ fragment.setContactIds(contactIds);
+ }
+ return fragment;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mContext = activity;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ if (mDialog != null && mDialog.isShowing()) {
+ mDialog.setOnDismissListener(null);
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ }
+
+ public void setContactIds(TreeSet<Long> contactIds) {
+ mContactIds = contactIds;
+ mIsLoaderActive = true;
+ if (isStarted()) {
+ Bundle args = new Bundle();
+ args.putSerializable(ARG_CONTACT_IDS, mContactIds);
+ getLoaderManager().restartLoader(R.id.dialog_delete_multiple_contact_loader_id,
+ args, this);
+ }
+ }
+
+ private boolean isStarted() {
+ return isAdded();
+ }
+
+ @Override
+ public void onStart() {
+ if (mIsLoaderActive) {
+ Bundle args = new Bundle();
+ args.putSerializable(ARG_CONTACT_IDS, mContactIds);
+ getLoaderManager().initLoader(
+ R.id.dialog_delete_multiple_contact_loader_id, args, this);
+ }
+ super.onStart();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mDialog != null) {
+ mDialog.hide();
+ }
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ final TreeSet<Long> contactIds = (TreeSet<Long>) args.getSerializable(ARG_CONTACT_IDS);
+ final Object[] parameterObject = contactIds.toArray();
+ final String[] parameters = new String[contactIds.size()];
+
+ final StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < contactIds.size(); i++) {
+ parameters[i] = String.valueOf(parameterObject[i]);
+ builder.append(RawContacts.CONTACT_ID + " =?");
+ if (i == contactIds.size() -1) {
+ break;
+ }
+ builder.append(" OR ");
+ }
+ return new CursorLoader(mContext, RawContacts.CONTENT_URI, RAW_CONTACT_PROJECTION,
+ builder.toString(), parameters, null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+
+ if (!mIsLoaderActive) {
+ return;
+ }
+
+ if (cursor == null || cursor.isClosed()) {
+ Log.e(TAG, "Failed to load contacts");
+ return;
+ }
+
+ // This cursor may contain duplicate raw contacts, so we need to de-dupe them first
+ final HashSet<Long> readOnlyRawContacts = Sets.newHashSet();
+ final HashSet<Long> writableRawContacts = Sets.newHashSet();
+ final HashSet<Long> contactIds = Sets.newHashSet();
+
+ AccountTypeManager accountTypes = AccountTypeManager.getInstance(getActivity());
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ final long rawContactId = cursor.getLong(COLUMN_INDEX_RAW_CONTACT_ID);
+ final String accountType = cursor.getString(COLUMN_INDEX_ACCOUNT_TYPE);
+ final String dataSet = cursor.getString(COLUMN_INDEX_DATA_SET);
+ final long contactId = cursor.getLong(COLUMN_INDEX_CONTACT_ID);
+ contactIds.add(contactId);
+ final AccountType type = accountTypes.getAccountType(accountType, dataSet);
+ boolean writable = type == null || type.areContactsWritable();
+ if (writable) {
+ writableRawContacts.add(rawContactId);
+ } else {
+ readOnlyRawContacts.add(rawContactId);
+ }
+ }
+
+ final int readOnlyCount = readOnlyRawContacts.size();
+ final int writableCount = writableRawContacts.size();
+
+ final int messageId;
+ if (readOnlyCount > 0 && writableCount > 0) {
+ messageId = R.string.batch_delete_multiple_accounts_confirmation;
+ } else if (readOnlyCount > 0 && writableCount == 0) {
+ messageId = R.string.batch_delete_read_only_contact_confirmation;
+ } else {
+ messageId = R.string.batch_delete_confirmation;
+ }
+
+ // Convert set of contact ids into a format that is easily parcellable and iterated upon
+ // for the sake of ContactSaveService.
+ final Long[] contactIdObjectArray = contactIds.toArray(new Long[contactIds.size()]);
+ final long[] contactIdArray = new long[contactIds.size()];
+ for (int i = 0; i < contactIds.size(); i++) {
+ contactIdArray[i] = contactIdObjectArray[i];
+ }
+
+ showDialog(messageId, contactIdArray);
+
+ // We don't want onLoadFinished() calls any more, which may come when the database is
+ // updating.
+ getLoaderManager().destroyLoader(R.id.dialog_delete_multiple_contact_loader_id);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ }
+
+ private void showDialog(int messageId, final long[] contactIds) {
+ mDialog = new AlertDialog.Builder(getActivity())
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setMessage(messageId)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ doDeleteContact(contactIds);
+ }
+ }
+ )
+ .create();
+
+ mDialog.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mIsLoaderActive = false;
+ mDialog = null;
+ }
+ });
+ mDialog.show();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_ACTIVE, mIsLoaderActive);
+ outState.putSerializable(KEY_CONTACTS_IDS, mContactIds);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if (savedInstanceState != null) {
+ mIsLoaderActive = savedInstanceState.getBoolean(KEY_ACTIVE);
+ mContactIds = (TreeSet<Long>) savedInstanceState.getSerializable(KEY_CONTACTS_IDS);
+ }
+ }
+
+ protected void doDeleteContact(long[] contactIds) {
+ mContext.startService(ContactSaveService.createDeleteMultipleContactsIntent(mContext,
+ contactIds));
+ notifyListenerActivity();
+ }
+
+ private void notifyListenerActivity() {
+ if (getActivity() instanceof MultiContactDeleteListener) {
+ final MultiContactDeleteListener listener = (MultiContactDeleteListener) getActivity();
+ listener.onDeletionFinished();
+ }
+ }
+}
diff --git a/src/com/android/contacts/list/MultiSelectContactsListFragment.java b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
index 5e17aee..9716ae0 100644
--- a/src/com/android/contacts/list/MultiSelectContactsListFragment.java
+++ b/src/com/android/contacts/list/MultiSelectContactsListFragment.java
@@ -92,9 +92,14 @@
public void displayCheckBoxes(boolean displayCheckBoxes) {
getAdapter().setDisplayCheckBoxes(displayCheckBoxes);
if (!displayCheckBoxes) {
- getAdapter().setSelectedContactIds(new TreeSet<Long>());
+ clearCheckBoxes();
}
}
+
+ public void clearCheckBoxes() {
+ getAdapter().setSelectedContactIds(new TreeSet<Long>());
+ }
+
@Override
protected boolean onItemLongClick(int position, long id) {
final MultiSelectEntryContactListAdapter adapter = getAdapter();