| /* |
| * Copyright (C) 2010 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 android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.Fragment; |
| import android.app.FragmentManager; |
| import android.app.LoaderManager; |
| 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.icu.text.MessageFormat; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.ContactsContract.Contacts; |
| import android.provider.ContactsContract.Contacts.Entity; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.widget.Toast; |
| |
| import com.android.contacts.ContactSaveService; |
| import com.android.contacts.R; |
| import com.android.contacts.model.AccountTypeManager; |
| import com.android.contacts.model.account.AccountType; |
| import com.android.contacts.preference.ContactsPreferences; |
| import com.android.contacts.util.ContactDisplayUtils; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.Sets; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| /** |
| * An interaction invoked to delete a contact. |
| */ |
| public class ContactDeletionInteraction extends Fragment |
| implements LoaderCallbacks<Cursor>, OnDismissListener { |
| |
| private static final String TAG = "ContactDeletion"; |
| private static final String FRAGMENT_TAG = "deleteContact"; |
| |
| private static final String KEY_ACTIVE = "active"; |
| private static final String KEY_CONTACT_URI = "contactUri"; |
| private static final String KEY_FINISH_WHEN_DONE = "finishWhenDone"; |
| public static final String ARG_CONTACT_URI = "contactUri"; |
| public static final int RESULT_CODE_DELETED = 3; |
| |
| private static final String[] ENTITY_PROJECTION = new String[] { |
| Entity.RAW_CONTACT_ID, //0 |
| Entity.ACCOUNT_TYPE, //1 |
| Entity.DATA_SET, // 2 |
| Entity.CONTACT_ID, // 3 |
| Entity.LOOKUP_KEY, // 4 |
| Entity.DISPLAY_NAME, // 5 |
| Entity.DISPLAY_NAME_ALTERNATIVE, // 6 |
| }; |
| |
| 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 static final int COLUMN_INDEX_LOOKUP_KEY = 4; |
| private static final int COLUMN_INDEX_DISPLAY_NAME = 5; |
| private static final int COLUMN_INDEX_DISPLAY_NAME_ALT = 6; |
| |
| private boolean mActive; |
| private Uri mContactUri; |
| private String mDisplayName; |
| private String mDisplayNameAlt; |
| private boolean mFinishActivityWhenDone; |
| private Context mContext; |
| private AlertDialog mDialog; |
| |
| /** This is a wrapper around the fragment's loader manager to be used only during testing. */ |
| private TestLoaderManagerBase mTestLoaderManager; |
| |
| @VisibleForTesting |
| int mMessageId; |
| |
| /** |
| * Starts the interaction. |
| * |
| * @param activity the activity within which to start the interaction |
| * @param contactUri the URI of the contact to delete |
| * @param finishActivityWhenDone whether to finish the activity upon completion of the |
| * interaction |
| * @return the newly created interaction |
| */ |
| public static ContactDeletionInteraction start( |
| Activity activity, Uri contactUri, boolean finishActivityWhenDone) { |
| return startWithTestLoaderManager(activity, contactUri, finishActivityWhenDone, null); |
| } |
| |
| /** |
| * Starts the interaction and optionally set up a {@link TestLoaderManagerBase}. |
| * |
| * @param activity the activity within which to start the interaction |
| * @param contactUri the URI of the contact to delete |
| * @param finishActivityWhenDone whether to finish the activity upon completion of the |
| * interaction |
| * @param testLoaderManager the {@link TestLoaderManagerBase} to use to load the data, may be null |
| * in which case the default {@link LoaderManager} is used |
| * @return the newly created interaction |
| */ |
| @VisibleForTesting |
| static ContactDeletionInteraction startWithTestLoaderManager( |
| Activity activity, Uri contactUri, boolean finishActivityWhenDone, |
| TestLoaderManagerBase testLoaderManager) { |
| if (contactUri == null || activity.isDestroyed()) { |
| return null; |
| } |
| |
| FragmentManager fragmentManager = activity.getFragmentManager(); |
| ContactDeletionInteraction fragment = |
| (ContactDeletionInteraction) fragmentManager.findFragmentByTag(FRAGMENT_TAG); |
| if (fragment == null) { |
| fragment = new ContactDeletionInteraction(); |
| fragment.setTestLoaderManager(testLoaderManager); |
| fragment.setContactUri(contactUri); |
| fragment.setFinishActivityWhenDone(finishActivityWhenDone); |
| fragmentManager.beginTransaction().add(fragment, FRAGMENT_TAG) |
| .commitAllowingStateLoss(); |
| } else { |
| fragment.setTestLoaderManager(testLoaderManager); |
| fragment.setContactUri(contactUri); |
| fragment.setFinishActivityWhenDone(finishActivityWhenDone); |
| } |
| return fragment; |
| } |
| |
| @Override |
| public LoaderManager getLoaderManager() { |
| // Return the TestLoaderManager if one is set up. |
| LoaderManager loaderManager = super.getLoaderManager(); |
| if (mTestLoaderManager != null) { |
| // Set the delegate: this operation is idempotent, so let's just do it every time. |
| mTestLoaderManager.setDelegate(loaderManager); |
| return mTestLoaderManager; |
| } else { |
| return loaderManager; |
| } |
| } |
| |
| /** Sets the TestLoaderManager that is used to wrap the actual LoaderManager in tests. */ |
| private void setTestLoaderManager(TestLoaderManagerBase mockLoaderManager) { |
| mTestLoaderManager = mockLoaderManager; |
| } |
| |
| @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 setContactUri(Uri contactUri) { |
| mContactUri = contactUri; |
| mActive = true; |
| if (isStarted()) { |
| Bundle args = new Bundle(); |
| args.putParcelable(ARG_CONTACT_URI, mContactUri); |
| getLoaderManager().restartLoader(R.id.dialog_delete_contact_loader_id, args, this); |
| } |
| } |
| |
| private void setFinishActivityWhenDone(boolean finishActivityWhenDone) { |
| this.mFinishActivityWhenDone = finishActivityWhenDone; |
| |
| } |
| |
| /* Visible for testing */ |
| boolean isStarted() { |
| return isAdded(); |
| } |
| |
| @Override |
| public void onStart() { |
| if (mActive) { |
| Bundle args = new Bundle(); |
| args.putParcelable(ARG_CONTACT_URI, mContactUri); |
| getLoaderManager().initLoader(R.id.dialog_delete_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) { |
| Uri contactUri = args.getParcelable(ARG_CONTACT_URI); |
| return new CursorLoader(mContext, |
| Uri.withAppendedPath(contactUri, Entity.CONTENT_DIRECTORY), ENTITY_PROJECTION, |
| null, null, null); |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { |
| if (mDialog != null) { |
| mDialog.dismiss(); |
| mDialog = null; |
| } |
| |
| if (!mActive) { |
| return; |
| } |
| |
| if (cursor == null || cursor.isClosed()) { |
| Log.e(TAG, "Failed to load contacts"); |
| return; |
| } |
| |
| long contactId = 0; |
| String lookupKey = null; |
| |
| // This cursor may contain duplicate raw contacts, so we need to de-dupe them first |
| HashSet<Long> readOnlyRawContacts = Sets.newHashSet(); |
| HashSet<Long> writableRawContacts = 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); |
| contactId = cursor.getLong(COLUMN_INDEX_CONTACT_ID); |
| lookupKey = cursor.getString(COLUMN_INDEX_LOOKUP_KEY); |
| mDisplayName = cursor.getString(COLUMN_INDEX_DISPLAY_NAME); |
| mDisplayNameAlt = cursor.getString(COLUMN_INDEX_DISPLAY_NAME_ALT); |
| AccountType type = accountTypes.getAccountType(accountType, dataSet); |
| boolean writable = type == null || type.areContactsWritable(); |
| if (writable) { |
| writableRawContacts.add(rawContactId); |
| } else { |
| readOnlyRawContacts.add(rawContactId); |
| } |
| } |
| if (TextUtils.isEmpty(lookupKey)) { |
| Log.e(TAG, "Failed to find contact lookup key"); |
| getActivity().finish(); |
| return; |
| } |
| |
| int readOnlyCount = readOnlyRawContacts.size(); |
| int writableCount = writableRawContacts.size(); |
| int positiveButtonId = android.R.string.ok; |
| if (readOnlyCount > 0 && writableCount > 0) { |
| mMessageId = R.string.readOnlyContactDeleteConfirmation; |
| } else if (readOnlyCount > 0 && writableCount == 0) { |
| mMessageId = R.string.readOnlyContactWarning; |
| positiveButtonId = R.string.readOnlyContactWarning_positive_button; |
| } else if (readOnlyCount == 0 && writableCount > 1) { |
| mMessageId = R.string.multipleContactDeleteConfirmation; |
| positiveButtonId = R.string.deleteConfirmation_positive_button; |
| } else { |
| mMessageId = R.string.deleteConfirmation; |
| positiveButtonId = R.string.deleteConfirmation_positive_button; |
| } |
| |
| final Uri contactUri = Contacts.getLookupUri(contactId, lookupKey); |
| showDialog(mMessageId, positiveButtonId, contactUri); |
| |
| // We don't want onLoadFinished() calls any more, which may come when the database is |
| // updating. |
| getLoaderManager().destroyLoader(R.id.dialog_delete_contact_loader_id); |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<Cursor> loader) { |
| } |
| |
| private void showDialog(int messageId, int positiveButtonId, final Uri contactUri) { |
| mDialog = new AlertDialog.Builder(getActivity()) |
| .setIconAttribute(android.R.attr.alertDialogIcon) |
| .setMessage(messageId) |
| .setNegativeButton(android.R.string.cancel, null) |
| .setPositiveButton(positiveButtonId, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int whichButton) { |
| doDeleteContact(contactUri); |
| } |
| } |
| ) |
| .create(); |
| |
| mDialog.setOnDismissListener(this); |
| mDialog.show(); |
| } |
| |
| @Override |
| public void onDismiss(DialogInterface dialog) { |
| mActive = false; |
| mDialog = null; |
| } |
| |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| outState.putBoolean(KEY_ACTIVE, mActive); |
| outState.putParcelable(KEY_CONTACT_URI, mContactUri); |
| outState.putBoolean(KEY_FINISH_WHEN_DONE, mFinishActivityWhenDone); |
| } |
| |
| @Override |
| public void onActivityCreated(Bundle savedInstanceState) { |
| super.onActivityCreated(savedInstanceState); |
| if (savedInstanceState != null) { |
| mActive = savedInstanceState.getBoolean(KEY_ACTIVE); |
| mContactUri = savedInstanceState.getParcelable(KEY_CONTACT_URI); |
| mFinishActivityWhenDone = savedInstanceState.getBoolean(KEY_FINISH_WHEN_DONE); |
| } |
| } |
| |
| protected void doDeleteContact(Uri contactUri) { |
| mContext.startService(ContactSaveService.createDeleteContactIntent(mContext, contactUri)); |
| if (isAdded() && mFinishActivityWhenDone) { |
| getActivity().setResult(RESULT_CODE_DELETED); |
| getActivity().finish(); |
| final String deleteToastMessage; |
| final String name = ContactDisplayUtils.getPreferredDisplayName(mDisplayName, |
| mDisplayNameAlt, new ContactsPreferences(mContext)); |
| if (TextUtils.isEmpty(name)) { |
| MessageFormat msgFormat = new MessageFormat( |
| getResources().getString(R.string.contacts_deleted_toast), |
| Locale.getDefault()); |
| Map<String, Object> arguments = new HashMap<>(); |
| arguments.put("count", 1); |
| deleteToastMessage = msgFormat.format(arguments); |
| } else { |
| deleteToastMessage = getResources().getString( |
| R.string.contacts_deleted_one_named_toast, name); |
| } |
| Toast.makeText(mContext, deleteToastMessage, Toast.LENGTH_LONG).show(); |
| } |
| } |
| } |