• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onDeletionFinished()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      */
start( Activity activity, TreeSet<Long> contactIds)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
onAttach(Activity activity)107     public void onAttach(Activity activity) {
108         super.onAttach(activity);
109         mContext = activity;
110     }
111 
112     @Override
onDestroyView()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 
setContactIds(TreeSet<Long> contactIds)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 
isStarted()133     private boolean isStarted() {
134         return isAdded();
135     }
136 
137     @Override
onStart()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
onStop()149     public void onStop() {
150         super.onStop();
151         if (mDialog != null) {
152             mDialog.hide();
153         }
154     }
155 
156     @Override
onCreateLoader(int id, Bundle args)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
onLoadFinished(Loader<Cursor> loader, Cursor cursor)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         if (readOnlyCount > 0 && writableCount > 0) {
218             messageId = R.string.batch_delete_multiple_accounts_confirmation;
219         } else if (readOnlyCount > 0 && writableCount == 0) {
220             messageId = R.string.batch_delete_read_only_contact_confirmation;
221         } else {
222             messageId = R.string.batch_delete_confirmation;
223         }
224 
225         // Convert set of contact ids into a format that is easily parcellable and iterated upon
226         // for the sake of ContactSaveService.
227         final Long[] contactIdObjectArray = contactIds.toArray(new Long[contactIds.size()]);
228         final long[] contactIdArray = new long[contactIds.size()];
229         for (int i = 0; i < contactIds.size(); i++) {
230             contactIdArray[i] = contactIdObjectArray[i];
231         }
232 
233         showDialog(messageId, contactIdArray);
234 
235         // We don't want onLoadFinished() calls any more, which may come when the database is
236         // updating.
237         getLoaderManager().destroyLoader(R.id.dialog_delete_multiple_contact_loader_id);
238     }
239 
240     @Override
onLoaderReset(Loader<Cursor> loader)241     public void onLoaderReset(Loader<Cursor> loader) {
242     }
243 
showDialog(int messageId, final long[] contactIds)244     private void showDialog(int messageId, final long[] contactIds) {
245         mDialog = new AlertDialog.Builder(getActivity())
246                 .setIconAttribute(android.R.attr.alertDialogIcon)
247                 .setMessage(messageId)
248                 .setNegativeButton(android.R.string.cancel, null)
249                 .setPositiveButton(android.R.string.ok,
250                     new DialogInterface.OnClickListener() {
251                         @Override
252                         public void onClick(DialogInterface dialog, int whichButton) {
253                             doDeleteContact(contactIds);
254                         }
255                     }
256                 )
257                 .create();
258 
259         mDialog.setOnDismissListener(new OnDismissListener() {
260             @Override
261             public void onDismiss(DialogInterface dialog) {
262                 mIsLoaderActive = false;
263                 mDialog = null;
264             }
265         });
266         mDialog.show();
267     }
268 
269     @Override
onSaveInstanceState(Bundle outState)270     public void onSaveInstanceState(Bundle outState) {
271         super.onSaveInstanceState(outState);
272         outState.putBoolean(KEY_ACTIVE, mIsLoaderActive);
273         outState.putSerializable(KEY_CONTACTS_IDS, mContactIds);
274     }
275 
276     @Override
onActivityCreated(Bundle savedInstanceState)277     public void onActivityCreated(Bundle savedInstanceState) {
278         super.onActivityCreated(savedInstanceState);
279         if (savedInstanceState != null) {
280             mIsLoaderActive = savedInstanceState.getBoolean(KEY_ACTIVE);
281             mContactIds = (TreeSet<Long>) savedInstanceState.getSerializable(KEY_CONTACTS_IDS);
282         }
283     }
284 
doDeleteContact(long[] contactIds)285     protected void doDeleteContact(long[] contactIds) {
286         mContext.startService(ContactSaveService.createDeleteMultipleContactsIntent(mContext,
287                 contactIds));
288         notifyListenerActivity();
289     }
290 
notifyListenerActivity()291     private void notifyListenerActivity() {
292         if (getActivity() instanceof MultiContactDeleteListener) {
293             final MultiContactDeleteListener listener = (MultiContactDeleteListener) getActivity();
294             listener.onDeletionFinished();
295         }
296     }
297 }
298