Home | History | Annotate | Download | only in interactions
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.contacts.interactions;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.Fragment;
     22 import android.app.FragmentManager;
     23 import android.app.LoaderManager;
     24 import android.app.LoaderManager.LoaderCallbacks;
     25 import android.content.Context;
     26 import android.content.CursorLoader;
     27 import android.content.DialogInterface;
     28 import android.content.DialogInterface.OnDismissListener;
     29 import android.content.Loader;
     30 import android.database.Cursor;
     31 import android.net.Uri;
     32 import android.os.Bundle;
     33 import android.provider.ContactsContract.Contacts;
     34 import android.provider.ContactsContract.Contacts.Entity;
     35 import android.text.TextUtils;
     36 import android.util.Log;
     37 import android.widget.Toast;
     38 
     39 import com.android.contacts.ContactSaveService;
     40 import com.android.contacts.R;
     41 import com.android.contacts.model.AccountTypeManager;
     42 import com.android.contacts.model.account.AccountType;
     43 import com.android.contacts.preference.ContactsPreferences;
     44 import com.android.contacts.util.ContactDisplayUtils;
     45 
     46 import com.google.common.annotations.VisibleForTesting;
     47 import com.google.common.collect.Sets;
     48 
     49 import java.util.HashSet;
     50 
     51 /**
     52  * An interaction invoked to delete a contact.
     53  */
     54 public class ContactDeletionInteraction extends Fragment
     55         implements LoaderCallbacks<Cursor>, OnDismissListener {
     56 
     57     private static final String TAG = "ContactDeletion";
     58     private static final String FRAGMENT_TAG = "deleteContact";
     59 
     60     private static final String KEY_ACTIVE = "active";
     61     private static final String KEY_CONTACT_URI = "contactUri";
     62     private static final String KEY_FINISH_WHEN_DONE = "finishWhenDone";
     63     public static final String ARG_CONTACT_URI = "contactUri";
     64     public static final int RESULT_CODE_DELETED = 3;
     65 
     66     private static final String[] ENTITY_PROJECTION = new String[] {
     67         Entity.RAW_CONTACT_ID, //0
     68         Entity.ACCOUNT_TYPE, //1
     69         Entity.DATA_SET, // 2
     70         Entity.CONTACT_ID, // 3
     71         Entity.LOOKUP_KEY, // 4
     72         Entity.DISPLAY_NAME, // 5
     73         Entity.DISPLAY_NAME_ALTERNATIVE, // 6
     74     };
     75 
     76     private static final int COLUMN_INDEX_RAW_CONTACT_ID = 0;
     77     private static final int COLUMN_INDEX_ACCOUNT_TYPE = 1;
     78     private static final int COLUMN_INDEX_DATA_SET = 2;
     79     private static final int COLUMN_INDEX_CONTACT_ID = 3;
     80     private static final int COLUMN_INDEX_LOOKUP_KEY = 4;
     81     private static final int COLUMN_INDEX_DISPLAY_NAME = 5;
     82     private static final int COLUMN_INDEX_DISPLAY_NAME_ALT = 6;
     83 
     84     private boolean mActive;
     85     private Uri mContactUri;
     86     private String mDisplayName;
     87     private String mDisplayNameAlt;
     88     private boolean mFinishActivityWhenDone;
     89     private Context mContext;
     90     private AlertDialog mDialog;
     91 
     92     /** This is a wrapper around the fragment's loader manager to be used only during testing. */
     93     private TestLoaderManagerBase mTestLoaderManager;
     94 
     95     @VisibleForTesting
     96     int mMessageId;
     97 
     98     /**
     99      * Starts the interaction.
    100      *
    101      * @param activity the activity within which to start the interaction
    102      * @param contactUri the URI of the contact to delete
    103      * @param finishActivityWhenDone whether to finish the activity upon completion of the
    104      *        interaction
    105      * @return the newly created interaction
    106      */
    107     public static ContactDeletionInteraction start(
    108             Activity activity, Uri contactUri, boolean finishActivityWhenDone) {
    109         return startWithTestLoaderManager(activity, contactUri, finishActivityWhenDone, null);
    110     }
    111 
    112     /**
    113      * Starts the interaction and optionally set up a {@link TestLoaderManagerBase}.
    114      *
    115      * @param activity the activity within which to start the interaction
    116      * @param contactUri the URI of the contact to delete
    117      * @param finishActivityWhenDone whether to finish the activity upon completion of the
    118      *        interaction
    119      * @param testLoaderManager the {@link TestLoaderManagerBase} to use to load the data, may be null
    120      *        in which case the default {@link LoaderManager} is used
    121      * @return the newly created interaction
    122      */
    123     @VisibleForTesting
    124     static ContactDeletionInteraction startWithTestLoaderManager(
    125             Activity activity, Uri contactUri, boolean finishActivityWhenDone,
    126             TestLoaderManagerBase testLoaderManager) {
    127         if (contactUri == null || activity.isDestroyed()) {
    128             return null;
    129         }
    130 
    131         FragmentManager fragmentManager = activity.getFragmentManager();
    132         ContactDeletionInteraction fragment =
    133                 (ContactDeletionInteraction) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
    134         if (fragment == null) {
    135             fragment = new ContactDeletionInteraction();
    136             fragment.setTestLoaderManager(testLoaderManager);
    137             fragment.setContactUri(contactUri);
    138             fragment.setFinishActivityWhenDone(finishActivityWhenDone);
    139             fragmentManager.beginTransaction().add(fragment, FRAGMENT_TAG)
    140                     .commitAllowingStateLoss();
    141         } else {
    142             fragment.setTestLoaderManager(testLoaderManager);
    143             fragment.setContactUri(contactUri);
    144             fragment.setFinishActivityWhenDone(finishActivityWhenDone);
    145         }
    146         return fragment;
    147     }
    148 
    149     @Override
    150     public LoaderManager getLoaderManager() {
    151         // Return the TestLoaderManager if one is set up.
    152         LoaderManager loaderManager = super.getLoaderManager();
    153         if (mTestLoaderManager != null) {
    154             // Set the delegate: this operation is idempotent, so let's just do it every time.
    155             mTestLoaderManager.setDelegate(loaderManager);
    156             return mTestLoaderManager;
    157         } else {
    158             return loaderManager;
    159         }
    160     }
    161 
    162     /** Sets the TestLoaderManager that is used to wrap the actual LoaderManager in tests. */
    163     private void setTestLoaderManager(TestLoaderManagerBase mockLoaderManager) {
    164         mTestLoaderManager = mockLoaderManager;
    165     }
    166 
    167     @Override
    168     public void onAttach(Activity activity) {
    169         super.onAttach(activity);
    170         mContext = activity;
    171     }
    172 
    173     @Override
    174     public void onDestroyView() {
    175         super.onDestroyView();
    176         if (mDialog != null && mDialog.isShowing()) {
    177             mDialog.setOnDismissListener(null);
    178             mDialog.dismiss();
    179             mDialog = null;
    180         }
    181     }
    182 
    183     public void setContactUri(Uri contactUri) {
    184         mContactUri = contactUri;
    185         mActive = true;
    186         if (isStarted()) {
    187             Bundle args = new Bundle();
    188             args.putParcelable(ARG_CONTACT_URI, mContactUri);
    189             getLoaderManager().restartLoader(R.id.dialog_delete_contact_loader_id, args, this);
    190         }
    191     }
    192 
    193     private void setFinishActivityWhenDone(boolean finishActivityWhenDone) {
    194         this.mFinishActivityWhenDone = finishActivityWhenDone;
    195 
    196     }
    197 
    198     /* Visible for testing */
    199     boolean isStarted() {
    200         return isAdded();
    201     }
    202 
    203     @Override
    204     public void onStart() {
    205         if (mActive) {
    206             Bundle args = new Bundle();
    207             args.putParcelable(ARG_CONTACT_URI, mContactUri);
    208             getLoaderManager().initLoader(R.id.dialog_delete_contact_loader_id, args, this);
    209         }
    210         super.onStart();
    211     }
    212 
    213     @Override
    214     public void onStop() {
    215         super.onStop();
    216         if (mDialog != null) {
    217             mDialog.hide();
    218         }
    219     }
    220 
    221     @Override
    222     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    223         Uri contactUri = args.getParcelable(ARG_CONTACT_URI);
    224         return new CursorLoader(mContext,
    225                 Uri.withAppendedPath(contactUri, Entity.CONTENT_DIRECTORY), ENTITY_PROJECTION,
    226                 null, null, null);
    227     }
    228 
    229     @Override
    230     public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    231         if (mDialog != null) {
    232             mDialog.dismiss();
    233             mDialog = null;
    234         }
    235 
    236         if (!mActive) {
    237             return;
    238         }
    239 
    240         if (cursor == null || cursor.isClosed()) {
    241             Log.e(TAG, "Failed to load contacts");
    242             return;
    243         }
    244 
    245         long contactId = 0;
    246         String lookupKey = null;
    247 
    248         // This cursor may contain duplicate raw contacts, so we need to de-dupe them first
    249         HashSet<Long>  readOnlyRawContacts = Sets.newHashSet();
    250         HashSet<Long>  writableRawContacts = Sets.newHashSet();
    251 
    252         AccountTypeManager accountTypes = AccountTypeManager.getInstance(getActivity());
    253         cursor.moveToPosition(-1);
    254         while (cursor.moveToNext()) {
    255             final long rawContactId = cursor.getLong(COLUMN_INDEX_RAW_CONTACT_ID);
    256             final String accountType = cursor.getString(COLUMN_INDEX_ACCOUNT_TYPE);
    257             final String dataSet = cursor.getString(COLUMN_INDEX_DATA_SET);
    258             contactId = cursor.getLong(COLUMN_INDEX_CONTACT_ID);
    259             lookupKey = cursor.getString(COLUMN_INDEX_LOOKUP_KEY);
    260             mDisplayName = cursor.getString(COLUMN_INDEX_DISPLAY_NAME);
    261             mDisplayNameAlt = cursor.getString(COLUMN_INDEX_DISPLAY_NAME_ALT);
    262             AccountType type = accountTypes.getAccountType(accountType, dataSet);
    263             boolean writable = type == null || type.areContactsWritable();
    264             if (writable) {
    265                 writableRawContacts.add(rawContactId);
    266             } else {
    267                 readOnlyRawContacts.add(rawContactId);
    268             }
    269         }
    270         if (TextUtils.isEmpty(lookupKey)) {
    271             Log.e(TAG, "Failed to find contact lookup key");
    272             getActivity().finish();
    273             return;
    274         }
    275 
    276         int readOnlyCount = readOnlyRawContacts.size();
    277         int writableCount = writableRawContacts.size();
    278         int positiveButtonId = android.R.string.ok;
    279         if (readOnlyCount > 0 && writableCount > 0) {
    280             mMessageId = R.string.readOnlyContactDeleteConfirmation;
    281         } else if (readOnlyCount > 0 && writableCount == 0) {
    282             mMessageId = R.string.readOnlyContactWarning;
    283             positiveButtonId = R.string.readOnlyContactWarning_positive_button;
    284         } else if (readOnlyCount == 0 && writableCount > 1) {
    285             mMessageId = R.string.multipleContactDeleteConfirmation;
    286             positiveButtonId = R.string.deleteConfirmation_positive_button;
    287         } else {
    288             mMessageId = R.string.deleteConfirmation;
    289             positiveButtonId = R.string.deleteConfirmation_positive_button;
    290         }
    291 
    292         final Uri contactUri = Contacts.getLookupUri(contactId, lookupKey);
    293         showDialog(mMessageId, positiveButtonId, contactUri);
    294 
    295         // We don't want onLoadFinished() calls any more, which may come when the database is
    296         // updating.
    297         getLoaderManager().destroyLoader(R.id.dialog_delete_contact_loader_id);
    298     }
    299 
    300     @Override
    301     public void onLoaderReset(Loader<Cursor> loader) {
    302     }
    303 
    304     private void showDialog(int messageId, int positiveButtonId, final Uri contactUri) {
    305         mDialog = new AlertDialog.Builder(getActivity())
    306                 .setIconAttribute(android.R.attr.alertDialogIcon)
    307                 .setMessage(messageId)
    308                 .setNegativeButton(android.R.string.cancel, null)
    309                 .setPositiveButton(positiveButtonId,
    310                     new DialogInterface.OnClickListener() {
    311                         @Override
    312                         public void onClick(DialogInterface dialog, int whichButton) {
    313                             doDeleteContact(contactUri);
    314                         }
    315                     }
    316                 )
    317                 .create();
    318 
    319         mDialog.setOnDismissListener(this);
    320         mDialog.show();
    321     }
    322 
    323     @Override
    324     public void onDismiss(DialogInterface dialog) {
    325         mActive = false;
    326         mDialog = null;
    327     }
    328 
    329     @Override
    330     public void onSaveInstanceState(Bundle outState) {
    331         super.onSaveInstanceState(outState);
    332         outState.putBoolean(KEY_ACTIVE, mActive);
    333         outState.putParcelable(KEY_CONTACT_URI, mContactUri);
    334         outState.putBoolean(KEY_FINISH_WHEN_DONE, mFinishActivityWhenDone);
    335     }
    336 
    337     @Override
    338     public void onActivityCreated(Bundle savedInstanceState) {
    339         super.onActivityCreated(savedInstanceState);
    340         if (savedInstanceState != null) {
    341             mActive = savedInstanceState.getBoolean(KEY_ACTIVE);
    342             mContactUri = savedInstanceState.getParcelable(KEY_CONTACT_URI);
    343             mFinishActivityWhenDone = savedInstanceState.getBoolean(KEY_FINISH_WHEN_DONE);
    344         }
    345     }
    346 
    347     protected void doDeleteContact(Uri contactUri) {
    348         mContext.startService(ContactSaveService.createDeleteContactIntent(mContext, contactUri));
    349         if (isAdded() && mFinishActivityWhenDone) {
    350             getActivity().setResult(RESULT_CODE_DELETED);
    351             getActivity().finish();
    352             final String deleteToastMessage;
    353             final String name = ContactDisplayUtils.getPreferredDisplayName(mDisplayName,
    354                     mDisplayNameAlt, new ContactsPreferences(mContext));
    355             if (TextUtils.isEmpty(name)) {
    356                 deleteToastMessage = getResources().getQuantityString(
    357                         R.plurals.contacts_deleted_toast, /* quantity */ 1);
    358             } else {
    359                 deleteToastMessage = getResources().getString(
    360                         R.string.contacts_deleted_one_named_toast, name);
    361             }
    362             Toast.makeText(mContext, deleteToastMessage, Toast.LENGTH_LONG).show();
    363         }
    364     }
    365 }
    366