blob: 695e9f69445ba001e4fc4c19bc4da2599e9a07e8 [file] [log] [blame]
Brian Attwelld2962a32015-03-02 14:48:50 -08001/*
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
17package com.android.contacts.interactions;
18
Brian Attwelld2962a32015-03-02 14:48:50 -080019import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.Fragment;
22import android.app.FragmentManager;
23import android.app.LoaderManager.LoaderCallbacks;
24import android.content.Context;
25import android.content.CursorLoader;
26import android.content.DialogInterface;
27import android.content.DialogInterface.OnDismissListener;
28import android.content.Loader;
29import android.database.Cursor;
30import android.os.Bundle;
31import android.provider.ContactsContract.RawContacts;
James Laskeyf62b4882016-10-21 11:36:40 -070032import android.text.TextUtils;
Brian Attwelld2962a32015-03-02 14:48:50 -080033import android.util.Log;
34
Gary Mai0a49afa2016-12-05 15:53:58 -080035import com.android.contacts.ContactSaveService;
36import com.android.contacts.R;
37import com.android.contacts.model.AccountTypeManager;
38import com.android.contacts.model.account.AccountType;
39import com.android.contacts.preference.ContactsPreferences;
40import com.android.contacts.util.ContactDisplayUtils;
41
42import com.google.common.collect.Sets;
43
Brian Attwelld2962a32015-03-02 14:48:50 -080044import java.util.HashSet;
45import java.util.TreeSet;
46
47/**
48 * An interaction invoked to delete multiple contacts.
49 *
50 * This class is very similar to {@link ContactDeletionInteraction}.
51 */
52public 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 = "ContactMultiDeletionInteraction";
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,
James Laskeye5a140a2016-10-18 15:43:42 -070070 RawContacts.DISPLAY_NAME_PRIMARY,
71 RawContacts.DISPLAY_NAME_ALTERNATIVE
Brian Attwelld2962a32015-03-02 14:48:50 -080072 };
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;
James Laskeye5a140a2016-10-18 15:43:42 -070078 private static final int COLUMN_INDEX_DISPLAY_NAME = 4;
79 private static final int COLUMN_INDEX_DISPLAY_NAME_ALT = 5;
Brian Attwelld2962a32015-03-02 14:48:50 -080080
81 private boolean mIsLoaderActive;
82 private TreeSet<Long> mContactIds;
83 private Context mContext;
84 private AlertDialog mDialog;
Wenyi Wang587b8f22016-08-17 17:40:45 -070085 private MultiContactDeleteListener mListener;
Brian Attwelld2962a32015-03-02 14:48:50 -080086
87 /**
88 * Starts the interaction.
89 *
Wenyi Wang587b8f22016-08-17 17:40:45 -070090 * @param hostFragment the fragment within which to start the interaction
Brian Attwelld2962a32015-03-02 14:48:50 -080091 * @param contactIds the IDs of contacts to be deleted
92 * @return the newly created interaction
93 */
94 public static ContactMultiDeletionInteraction start(
Wenyi Wang587b8f22016-08-17 17:40:45 -070095 Fragment hostFragment, TreeSet<Long> contactIds) {
Brian Attwelld2962a32015-03-02 14:48:50 -080096 if (contactIds == null) {
97 return null;
98 }
99
Wenyi Wang587b8f22016-08-17 17:40:45 -0700100 final FragmentManager fragmentManager = hostFragment.getFragmentManager();
Brian Attwelld2962a32015-03-02 14:48:50 -0800101 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();
James Laskeye5a140a2016-10-18 15:43:42 -0700203 final HashSet<String> names = Sets.newHashSet();
204
205 final ContactsPreferences contactsPreferences = new ContactsPreferences(mContext);
Brian Attwelld2962a32015-03-02 14:48:50 -0800206
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);
James Laskeye5a140a2016-10-18 15:43:42 -0700214 final String displayName = cursor.getString(COLUMN_INDEX_DISPLAY_NAME);
215 final String displayNameAlt = cursor.getString(COLUMN_INDEX_DISPLAY_NAME_ALT);
216
James Laskeyf62b4882016-10-21 11:36:40 -0700217 final String name = ContactDisplayUtils.getPreferredDisplayName(displayName,
218 displayNameAlt, contactsPreferences);
219 if (!TextUtils.isEmpty(name)) {
220 names.add(name);
221 }
James Laskeye5a140a2016-10-18 15:43:42 -0700222
Brian Attwelld2962a32015-03-02 14:48:50 -0800223 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;
Tingting Wang168331d2015-10-30 12:00:57 -0700237 int positiveButtonId = android.R.string.ok;
Brian Attwelld2962a32015-03-02 14:48:50 -0800238 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;
Tingting Wang07bc6762015-10-30 15:19:38 -0700242 positiveButtonId = R.string.readOnlyContactWarning_positive_button;
Tingting Wang168331d2015-10-30 12:00:57 -0700243 } else if (writableCount == 1) {
244 messageId = R.string.single_delete_confirmation;
245 positiveButtonId = R.string.deleteConfirmation_positive_button;
Brian Attwelld2962a32015-03-02 14:48:50 -0800246 } else {
247 messageId = R.string.batch_delete_confirmation;
Tingting Wang168331d2015-10-30 12:00:57 -0700248 positiveButtonId = R.string.deleteConfirmation_positive_button;
Brian Attwelld2962a32015-03-02 14:48:50 -0800249 }
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
James Laskeye5a140a2016-10-18 15:43:42 -0700259 final String[] namesArray = names.toArray(new String[names.size()]);
260 showDialog(messageId, positiveButtonId, contactIdArray, namesArray);
Brian Attwelld2962a32015-03-02 14:48:50 -0800261
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
James Laskeye5a140a2016-10-18 15:43:42 -0700271 private void showDialog(int messageId, int positiveButtonId, final long[] contactIds,
272 final String[] namesArray) {
Brian Attwelld2962a32015-03-02 14:48:50 -0800273 mDialog = new AlertDialog.Builder(getActivity())
274 .setIconAttribute(android.R.attr.alertDialogIcon)
275 .setMessage(messageId)
276 .setNegativeButton(android.R.string.cancel, null)
Tingting Wang168331d2015-10-30 12:00:57 -0700277 .setPositiveButton(positiveButtonId,
Brian Attwelld2962a32015-03-02 14:48:50 -0800278 new DialogInterface.OnClickListener() {
279 @Override
280 public void onClick(DialogInterface dialog, int whichButton) {
James Laskeye5a140a2016-10-18 15:43:42 -0700281 doDeleteContact(contactIds, namesArray);
Brian Attwelld2962a32015-03-02 14:48:50 -0800282 }
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
James Laskeye5a140a2016-10-18 15:43:42 -0700313 protected void doDeleteContact(long[] contactIds, final String[] names) {
Brian Attwelld2962a32015-03-02 14:48:50 -0800314 mContext.startService(ContactSaveService.createDeleteMultipleContactsIntent(mContext,
James Laskeye5a140a2016-10-18 15:43:42 -0700315 contactIds, names));
Wenyi Wang587b8f22016-08-17 17:40:45 -0700316 mListener.onDeletionFinished();
Brian Attwelld2962a32015-03-02 14:48:50 -0800317 }
318
Wenyi Wang587b8f22016-08-17 17:40:45 -0700319 public void setListener(MultiContactDeleteListener listener) {
320 mListener = listener;
Brian Attwelld2962a32015-03-02 14:48:50 -0800321 }
322}