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