Home | History | Annotate | Download | only in interactions
      1 /*
      2  * Copyright (C) 2015 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.LoaderCallbacks;
     24 import android.content.Context;
     25 import android.content.CursorLoader;
     26 import android.content.DialogInterface;
     27 import android.content.DialogInterface.OnDismissListener;
     28 import android.content.Loader;
     29 import android.database.Cursor;
     30 import android.os.Bundle;
     31 import android.provider.ContactsContract.RawContacts;
     32 import android.text.TextUtils;
     33 import android.util.Log;
     34 
     35 import com.android.contacts.ContactSaveService;
     36 import com.android.contacts.R;
     37 import com.android.contacts.model.AccountTypeManager;
     38 import com.android.contacts.model.account.AccountType;
     39 import com.android.contacts.preference.ContactsPreferences;
     40 import com.android.contacts.util.ContactDisplayUtils;
     41 
     42 import com.google.common.collect.Sets;
     43 
     44 import java.util.HashSet;
     45 import java.util.TreeSet;
     46 
     47 /**
     48  * An interaction invoked to delete multiple contacts.
     49  *
     50  * This class is very similar to {@link ContactDeletionInteraction}.
     51  */
     52 public class ContactMultiDeletionInteraction extends Fragment
     53         implements LoaderCallbacks<Cursor> {
     54 
     55     public interface MultiContactDeleteListener {
     56         void onDeletionFinished();
     57     }
     58 
     59     private static final String FRAGMENT_TAG = "deleteMultipleContacts";
     60     private static final String TAG = "ContactMultiDeletion";
     61     private static final String KEY_ACTIVE = "active";
     62     private static final String KEY_CONTACTS_IDS = "contactIds";
     63     public static final String ARG_CONTACT_IDS = "contactIds";
     64 
     65     private static final String[] RAW_CONTACT_PROJECTION = new String[] {
     66             RawContacts._ID,
     67             RawContacts.ACCOUNT_TYPE,
     68             RawContacts.DATA_SET,
     69             RawContacts.CONTACT_ID,
     70             RawContacts.DISPLAY_NAME_PRIMARY,
     71             RawContacts.DISPLAY_NAME_ALTERNATIVE
     72     };
     73 
     74     private static final int COLUMN_INDEX_RAW_CONTACT_ID = 0;
     75     private static final int COLUMN_INDEX_ACCOUNT_TYPE = 1;
     76     private static final int COLUMN_INDEX_DATA_SET = 2;
     77     private static final int COLUMN_INDEX_CONTACT_ID = 3;
     78     private static final int COLUMN_INDEX_DISPLAY_NAME = 4;
     79     private static final int COLUMN_INDEX_DISPLAY_NAME_ALT = 5;
     80 
     81     private boolean mIsLoaderActive;
     82     private TreeSet<Long> mContactIds;
     83     private Context mContext;
     84     private AlertDialog mDialog;
     85     private MultiContactDeleteListener mListener;
     86 
     87     /**
     88      * Starts the interaction.
     89      *
     90      * @param hostFragment the fragment within which to start the interaction
     91      * @param contactIds the IDs of contacts to be deleted
     92      * @return the newly created interaction
     93      */
     94     public static ContactMultiDeletionInteraction start(
     95             Fragment hostFragment, TreeSet<Long> contactIds) {
     96         if (contactIds == null) {
     97             return null;
     98         }
     99 
    100         final FragmentManager fragmentManager = hostFragment.getFragmentManager();
    101         ContactMultiDeletionInteraction fragment =
    102                 (ContactMultiDeletionInteraction) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
    103         if (fragment == null) {
    104             fragment = new ContactMultiDeletionInteraction();
    105             fragment.setContactIds(contactIds);
    106             fragmentManager.beginTransaction().add(fragment, FRAGMENT_TAG)
    107                     .commitAllowingStateLoss();
    108         } else {
    109             fragment.setContactIds(contactIds);
    110         }
    111         return fragment;
    112     }
    113 
    114     @Override
    115     public void onAttach(Activity activity) {
    116         super.onAttach(activity);
    117         mContext = activity;
    118     }
    119 
    120     @Override
    121     public void onDestroyView() {
    122         super.onDestroyView();
    123         if (mDialog != null && mDialog.isShowing()) {
    124             mDialog.setOnDismissListener(null);
    125             mDialog.dismiss();
    126             mDialog = null;
    127         }
    128     }
    129 
    130     public void setContactIds(TreeSet<Long> contactIds) {
    131         mContactIds = contactIds;
    132         mIsLoaderActive = true;
    133         if (isStarted()) {
    134             Bundle args = new Bundle();
    135             args.putSerializable(ARG_CONTACT_IDS, mContactIds);
    136             getLoaderManager().restartLoader(R.id.dialog_delete_multiple_contact_loader_id,
    137                     args, this);
    138         }
    139     }
    140 
    141     private boolean isStarted() {
    142         return isAdded();
    143     }
    144 
    145     @Override
    146     public void onStart() {
    147         if (mIsLoaderActive) {
    148             Bundle args = new Bundle();
    149             args.putSerializable(ARG_CONTACT_IDS, mContactIds);
    150             getLoaderManager().initLoader(
    151                     R.id.dialog_delete_multiple_contact_loader_id, args, this);
    152         }
    153         super.onStart();
    154     }
    155 
    156     @Override
    157     public void onStop() {
    158         super.onStop();
    159         if (mDialog != null) {
    160             mDialog.hide();
    161         }
    162     }
    163 
    164     @Override
    165     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    166         final TreeSet<Long> contactIds = (TreeSet<Long>) args.getSerializable(ARG_CONTACT_IDS);
    167         final Object[] parameterObject = contactIds.toArray();
    168         final String[] parameters = new String[contactIds.size()];
    169 
    170         final StringBuilder builder = new StringBuilder();
    171         for (int i = 0; i < contactIds.size(); i++) {
    172             parameters[i] = String.valueOf(parameterObject[i]);
    173             builder.append(RawContacts.CONTACT_ID + " =?");
    174             if (i == contactIds.size() -1) {
    175                 break;
    176             }
    177             builder.append(" OR ");
    178         }
    179         return new CursorLoader(mContext, RawContacts.CONTENT_URI, RAW_CONTACT_PROJECTION,
    180                 builder.toString(), parameters, null);
    181     }
    182 
    183     @Override
    184     public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    185         if (mDialog != null) {
    186             mDialog.dismiss();
    187             mDialog = null;
    188         }
    189 
    190         if (!mIsLoaderActive) {
    191             return;
    192         }
    193 
    194         if (cursor == null || cursor.isClosed()) {
    195             Log.e(TAG, "Failed to load contacts");
    196             return;
    197         }
    198 
    199         // This cursor may contain duplicate raw contacts, so we need to de-dupe them first
    200         final HashSet<Long> readOnlyRawContacts = Sets.newHashSet();
    201         final HashSet<Long> writableRawContacts = Sets.newHashSet();
    202         final HashSet<Long> contactIds = Sets.newHashSet();
    203         final HashSet<String> names = Sets.newHashSet();
    204 
    205         final ContactsPreferences contactsPreferences = new ContactsPreferences(mContext);
    206 
    207         AccountTypeManager accountTypes = AccountTypeManager.getInstance(getActivity());
    208         cursor.moveToPosition(-1);
    209         while (cursor.moveToNext()) {
    210             final long rawContactId = cursor.getLong(COLUMN_INDEX_RAW_CONTACT_ID);
    211             final String accountType = cursor.getString(COLUMN_INDEX_ACCOUNT_TYPE);
    212             final String dataSet = cursor.getString(COLUMN_INDEX_DATA_SET);
    213             final long contactId = cursor.getLong(COLUMN_INDEX_CONTACT_ID);
    214             final String displayName = cursor.getString(COLUMN_INDEX_DISPLAY_NAME);
    215             final String displayNameAlt = cursor.getString(COLUMN_INDEX_DISPLAY_NAME_ALT);
    216 
    217             final String name = ContactDisplayUtils.getPreferredDisplayName(displayName,
    218                     displayNameAlt, contactsPreferences);
    219             if (!TextUtils.isEmpty(name)) {
    220                 names.add(name);
    221             }
    222 
    223             contactIds.add(contactId);
    224             final AccountType type = accountTypes.getAccountType(accountType, dataSet);
    225             boolean writable = type == null || type.areContactsWritable();
    226             if (writable) {
    227                 writableRawContacts.add(rawContactId);
    228             } else {
    229                 readOnlyRawContacts.add(rawContactId);
    230             }
    231         }
    232 
    233         final int readOnlyCount = readOnlyRawContacts.size();
    234         final int writableCount = writableRawContacts.size();
    235 
    236         final int messageId;
    237         int positiveButtonId = android.R.string.ok;
    238         if (readOnlyCount > 0 && writableCount > 0) {
    239             messageId = R.string.batch_delete_multiple_accounts_confirmation;
    240         } else if (readOnlyCount > 0 && writableCount == 0) {
    241             messageId = R.string.batch_delete_read_only_contact_confirmation;
    242             positiveButtonId = R.string.readOnlyContactWarning_positive_button;
    243         } else if (writableCount == 1) {
    244             messageId = R.string.single_delete_confirmation;
    245             positiveButtonId = R.string.deleteConfirmation_positive_button;
    246         } else {
    247             messageId = R.string.batch_delete_confirmation;
    248             positiveButtonId = R.string.deleteConfirmation_positive_button;
    249         }
    250 
    251         // Convert set of contact ids into a format that is easily parcellable and iterated upon
    252         // for the sake of ContactSaveService.
    253         final Long[] contactIdObjectArray = contactIds.toArray(new Long[contactIds.size()]);
    254         final long[] contactIdArray = new long[contactIds.size()];
    255         for (int i = 0; i < contactIds.size(); i++) {
    256             contactIdArray[i] = contactIdObjectArray[i];
    257         }
    258 
    259         final String[] namesArray = names.toArray(new String[names.size()]);
    260         showDialog(messageId, positiveButtonId, contactIdArray, namesArray);
    261 
    262         // We don't want onLoadFinished() calls any more, which may come when the database is
    263         // updating.
    264         getLoaderManager().destroyLoader(R.id.dialog_delete_multiple_contact_loader_id);
    265     }
    266 
    267     @Override
    268     public void onLoaderReset(Loader<Cursor> loader) {
    269     }
    270 
    271     private void showDialog(int messageId, int positiveButtonId, final long[] contactIds,
    272             final String[] namesArray) {
    273         mDialog = new AlertDialog.Builder(getActivity())
    274                 .setIconAttribute(android.R.attr.alertDialogIcon)
    275                 .setMessage(messageId)
    276                 .setNegativeButton(android.R.string.cancel, null)
    277                 .setPositiveButton(positiveButtonId,
    278                     new DialogInterface.OnClickListener() {
    279                         @Override
    280                         public void onClick(DialogInterface dialog, int whichButton) {
    281                             doDeleteContact(contactIds, namesArray);
    282                         }
    283                     }
    284                 )
    285                 .create();
    286 
    287         mDialog.setOnDismissListener(new OnDismissListener() {
    288             @Override
    289             public void onDismiss(DialogInterface dialog) {
    290                 mIsLoaderActive = false;
    291                 mDialog = null;
    292             }
    293         });
    294         mDialog.show();
    295     }
    296 
    297     @Override
    298     public void onSaveInstanceState(Bundle outState) {
    299         super.onSaveInstanceState(outState);
    300         outState.putBoolean(KEY_ACTIVE, mIsLoaderActive);
    301         outState.putSerializable(KEY_CONTACTS_IDS, mContactIds);
    302     }
    303 
    304     @Override
    305     public void onActivityCreated(Bundle savedInstanceState) {
    306         super.onActivityCreated(savedInstanceState);
    307         if (savedInstanceState != null) {
    308             mIsLoaderActive = savedInstanceState.getBoolean(KEY_ACTIVE);
    309             mContactIds = (TreeSet<Long>) savedInstanceState.getSerializable(KEY_CONTACTS_IDS);
    310         }
    311     }
    312 
    313     protected void doDeleteContact(long[] contactIds, final String[] names) {
    314         mContext.startService(ContactSaveService.createDeleteMultipleContactsIntent(mContext,
    315                 contactIds, names));
    316         mListener.onDeletionFinished();
    317     }
    318 
    319     public void setListener(MultiContactDeleteListener listener) {
    320         mListener = listener;
    321     }
    322 }
    323