blob: ead7010ede29fe00c5f904f488ae897da2cab171 [file] [log] [blame]
Dmitri Plotnikov9692f262010-06-29 18:53:11 -07001/*
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
17package com.android.contacts.interactions;
18
Dmitri Plotnikov9692f262010-06-29 18:53:11 -070019import android.app.Activity;
20import android.app.AlertDialog;
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -080021import android.app.Fragment;
22import android.app.FragmentManager;
Flavio Lerda34ce5e92011-06-06 15:25:17 +010023import android.app.LoaderManager;
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -080024import android.app.LoaderManager.LoaderCallbacks;
Dmitri Plotnikov9692f262010-06-29 18:53:11 -070025import android.content.Context;
26import android.content.CursorLoader;
27import android.content.DialogInterface;
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -080028import android.content.DialogInterface.OnDismissListener;
Dmitri Plotnikov9692f262010-06-29 18:53:11 -070029import android.content.Loader;
30import android.database.Cursor;
31import android.net.Uri;
32import android.os.Bundle;
Dmitri Plotnikov355d0f42010-11-03 17:13:51 -070033import android.provider.ContactsContract.Contacts;
34import android.provider.ContactsContract.Contacts.Entity;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070035
36import com.android.contacts.ContactSaveService;
37import com.android.contacts.R;
38import com.android.contacts.model.AccountType;
39import com.android.contacts.model.AccountTypeManager;
40import com.google.common.annotations.VisibleForTesting;
41import com.google.common.collect.Sets;
Dmitri Plotnikov9692f262010-06-29 18:53:11 -070042
Dmitri Plotnikov355d0f42010-11-03 17:13:51 -070043import java.util.HashSet;
44
Dmitri Plotnikov9692f262010-06-29 18:53:11 -070045/**
46 * An interaction invoked to delete a contact.
47 */
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -080048public class ContactDeletionInteraction extends Fragment
49 implements LoaderCallbacks<Cursor>, OnDismissListener {
Dmitri Plotnikov9692f262010-06-29 18:53:11 -070050
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -080051 private static final String FRAGMENT_TAG = "deleteContact";
Dmitri Plotnikov9692f262010-06-29 18:53:11 -070052
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -080053 private static final String KEY_ACTIVE = "active";
Dmitri Plotnikovcc42b452011-01-20 11:44:44 -080054 private static final String KEY_CONTACT_URI = "contactUri";
Dmitri Plotnikovc9eda372011-02-23 08:58:13 -080055 private static final String KEY_FINISH_WHEN_DONE = "finishWhenDone";
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -080056 public static final String ARG_CONTACT_URI = "contactUri";
Dmitri Plotnikov9692f262010-06-29 18:53:11 -070057
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -080058 private static final String[] ENTITY_PROJECTION = new String[] {
59 Entity.RAW_CONTACT_ID, //0
60 Entity.ACCOUNT_TYPE, //1
Dave Santoro2b3f3c52011-07-26 17:35:42 -070061 Entity.DATA_SET, // 2
62 Entity.CONTACT_ID, // 3
63 Entity.LOOKUP_KEY, // 4
Dmitri Plotnikov9692f262010-06-29 18:53:11 -070064 };
65
Dmitri Plotnikov355d0f42010-11-03 17:13:51 -070066 private static final int COLUMN_INDEX_RAW_CONTACT_ID = 0;
67 private static final int COLUMN_INDEX_ACCOUNT_TYPE = 1;
Dave Santoro2b3f3c52011-07-26 17:35:42 -070068 private static final int COLUMN_INDEX_DATA_SET = 2;
69 private static final int COLUMN_INDEX_CONTACT_ID = 3;
70 private static final int COLUMN_INDEX_LOOKUP_KEY = 4;
Dmitri Plotnikov355d0f42010-11-03 17:13:51 -070071
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -080072 private boolean mActive;
73 private Uri mContactUri;
Dmitri Plotnikovc9eda372011-02-23 08:58:13 -080074 private boolean mFinishActivityWhenDone;
Dmitri Plotnikov9692f262010-06-29 18:53:11 -070075 private Context mContext;
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -080076 private AlertDialog mDialog;
77
Flavio Lerda34ce5e92011-06-06 15:25:17 +010078 /** This is a wrapper around the fragment's loader manager to be used only during testing. */
79 private TestLoaderManager mTestLoaderManager;
80
81 @VisibleForTesting
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -080082 int mMessageId;
83
Flavio Lerda34ce5e92011-06-06 15:25:17 +010084 /**
85 * Starts the interaction.
86 *
87 * @param activity the activity within which to start the interaction
88 * @param contactUri the URI of the contact to delete
89 * @param finishActivityWhenDone whether to finish the activity upon completion of the
90 * interaction
91 * @return the newly created interaction
92 */
Dmitri Plotnikovc9eda372011-02-23 08:58:13 -080093 public static ContactDeletionInteraction start(
94 Activity activity, Uri contactUri, boolean finishActivityWhenDone) {
Flavio Lerda34ce5e92011-06-06 15:25:17 +010095 return startWithTestLoaderManager(activity, contactUri, finishActivityWhenDone, null);
96 }
97
98 /**
99 * Starts the interaction and optionally set up a {@link TestLoaderManager}.
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 * @param testLoaderManager the {@link TestLoaderManager} to use to load the data, may be null
106 * in which case the default {@link LoaderManager} is used
107 * @return the newly created interaction
108 */
109 @VisibleForTesting
110 static ContactDeletionInteraction startWithTestLoaderManager(
111 Activity activity, Uri contactUri, boolean finishActivityWhenDone,
112 TestLoaderManager testLoaderManager) {
Dmitri Plotnikovac0e18e2011-02-23 17:23:53 -0800113 if (contactUri == null) {
114 return null;
115 }
116
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800117 FragmentManager fragmentManager = activity.getFragmentManager();
118 ContactDeletionInteraction fragment =
119 (ContactDeletionInteraction) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
120 if (fragment == null) {
121 fragment = new ContactDeletionInteraction();
Flavio Lerda34ce5e92011-06-06 15:25:17 +0100122 fragment.setTestLoaderManager(testLoaderManager);
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800123 fragment.setContactUri(contactUri);
Dmitri Plotnikovc9eda372011-02-23 08:58:13 -0800124 fragment.setFinishActivityWhenDone(finishActivityWhenDone);
Minh Pham4b25da72011-08-25 13:50:38 -0700125 fragmentManager.beginTransaction().add(fragment, FRAGMENT_TAG)
126 .commitAllowingStateLoss();
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800127 } else {
Flavio Lerda34ce5e92011-06-06 15:25:17 +0100128 fragment.setTestLoaderManager(testLoaderManager);
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800129 fragment.setContactUri(contactUri);
Dmitri Plotnikovc9eda372011-02-23 08:58:13 -0800130 fragment.setFinishActivityWhenDone(finishActivityWhenDone);
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800131 }
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -0800132 return fragment;
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800133 }
134
135 @Override
Flavio Lerda34ce5e92011-06-06 15:25:17 +0100136 public LoaderManager getLoaderManager() {
137 // Return the TestLoaderManager if one is set up.
138 LoaderManager loaderManager = super.getLoaderManager();
139 if (mTestLoaderManager != null) {
140 // Set the delegate: this operation is idempotent, so let's just do it every time.
141 mTestLoaderManager.setDelegate(loaderManager);
142 return mTestLoaderManager;
143 } else {
144 return loaderManager;
145 }
146 }
147
148 /** Sets the TestLoaderManager that is used to wrap the actual LoaderManager in tests. */
149 private void setTestLoaderManager(TestLoaderManager mockLoaderManager) {
150 mTestLoaderManager = mockLoaderManager;
151 }
152
153 @Override
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800154 public void onAttach(Activity activity) {
155 super.onAttach(activity);
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -0800156 mContext = activity;
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700157 }
158
Dmitri Plotnikov0279fca2011-02-23 17:47:31 -0800159 @Override
160 public void onDestroyView() {
161 super.onDestroyView();
162 if (mDialog != null && mDialog.isShowing()) {
163 mDialog.setOnDismissListener(null);
164 mDialog.dismiss();
165 mDialog = null;
166 }
167 }
168
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800169 public void setContactUri(Uri contactUri) {
170 mContactUri = contactUri;
171 mActive = true;
172 if (isStarted()) {
173 Bundle args = new Bundle();
174 args.putParcelable(ARG_CONTACT_URI, mContactUri);
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -0800175 getLoaderManager().restartLoader(R.id.dialog_delete_contact_loader_id, args, this);
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700176 }
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700177 }
178
Dmitri Plotnikovc9eda372011-02-23 08:58:13 -0800179 private void setFinishActivityWhenDone(boolean finishActivityWhenDone) {
180 this.mFinishActivityWhenDone = finishActivityWhenDone;
181
182 }
183
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800184 /* Visible for testing */
185 boolean isStarted() {
186 return isAdded();
187 }
188
189 @Override
190 public void onStart() {
191 if (mActive) {
192 Bundle args = new Bundle();
193 args.putParcelable(ARG_CONTACT_URI, mContactUri);
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -0800194 getLoaderManager().initLoader(R.id.dialog_delete_contact_loader_id, args, this);
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800195 }
196 super.onStart();
197 }
198
199 @Override
Dmitri Plotnikov0279fca2011-02-23 17:47:31 -0800200 public void onStop() {
201 super.onStop();
202 if (mDialog != null) {
203 mDialog.hide();
204 }
205 }
206
207 @Override
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800208 public Loader<Cursor> onCreateLoader(int id, Bundle args) {
209 Uri contactUri = args.getParcelable(ARG_CONTACT_URI);
210 return new CursorLoader(mContext,
211 Uri.withAppendedPath(contactUri, Entity.CONTENT_DIRECTORY), ENTITY_PROJECTION,
212 null, null, null);
213 }
214
215 @Override
216 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
217 if (mDialog != null) {
218 mDialog.dismiss();
219 mDialog = null;
220 }
221
222 if (!mActive) {
223 return;
224 }
225
Dmitri Plotnikov355d0f42010-11-03 17:13:51 -0700226 long contactId = 0;
227 String lookupKey = null;
228
229 // This cursor may contain duplicate raw contacts, so we need to de-dupe them first
230 HashSet<Long> readOnlyRawContacts = Sets.newHashSet();
231 HashSet<Long> writableRawContacts = Sets.newHashSet();
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700232
Dmitri Plotnikova07fa5f2011-01-09 11:56:50 -0800233 AccountTypeManager accountTypes = AccountTypeManager.getInstance(getActivity());
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800234 cursor.moveToPosition(-1);
235 while (cursor.moveToNext()) {
236 final long rawContactId = cursor.getLong(COLUMN_INDEX_RAW_CONTACT_ID);
237 final String accountType = cursor.getString(COLUMN_INDEX_ACCOUNT_TYPE);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700238 final String dataSet = cursor.getString(COLUMN_INDEX_DATA_SET);
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800239 contactId = cursor.getLong(COLUMN_INDEX_CONTACT_ID);
240 lookupKey = cursor.getString(COLUMN_INDEX_LOOKUP_KEY);
Dave Santoro2b3f3c52011-07-26 17:35:42 -0700241 AccountType type = accountTypes.getAccountType(accountType, dataSet);
Daniel Lehmann96e87fd2011-09-20 21:31:37 -0700242 boolean writable = type == null || type.areContactsWritable();
243 if (writable) {
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800244 writableRawContacts.add(rawContactId);
Daniel Lehmann96e87fd2011-09-20 21:31:37 -0700245 } else {
246 readOnlyRawContacts.add(rawContactId);
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700247 }
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700248 }
249
Dmitri Plotnikov355d0f42010-11-03 17:13:51 -0700250 int readOnlyCount = readOnlyRawContacts.size();
251 int writableCount = writableRawContacts.size();
252 if (readOnlyCount > 0 && writableCount > 0) {
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -0800253 mMessageId = R.string.readOnlyContactDeleteConfirmation;
Dmitri Plotnikov355d0f42010-11-03 17:13:51 -0700254 } else if (readOnlyCount > 0 && writableCount == 0) {
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -0800255 mMessageId = R.string.readOnlyContactWarning;
Dmitri Plotnikov355d0f42010-11-03 17:13:51 -0700256 } else if (readOnlyCount == 0 && writableCount > 1) {
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -0800257 mMessageId = R.string.multipleContactDeleteConfirmation;
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700258 } else {
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -0800259 mMessageId = R.string.deleteConfirmation;
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700260 }
261
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800262 final Uri contactUri = Contacts.getLookupUri(contactId, lookupKey);
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -0800263 showDialog(mMessageId, contactUri);
Daisuke Miyakawad7c148b2011-03-31 18:33:35 -0700264
265 // We don't want onLoadFinished() calls any more, which may come when the database is
266 // updating.
267 getLoaderManager().destroyLoader(R.id.dialog_delete_contact_loader_id);
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700268 }
269
Daisuke Miyakawad7c148b2011-03-31 18:33:35 -0700270 @Override
Dianne Hackborn4ef95cc2010-12-16 00:44:33 -0800271 public void onLoaderReset(Loader<Cursor> loader) {
272 }
Dmitri Plotnikov69f9e6f2011-01-03 15:47:24 -0800273
Dmitri Plotnikov43fd1e82011-01-09 11:29:39 -0800274 private void showDialog(int messageId, final Uri contactUri) {
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800275 mDialog = new AlertDialog.Builder(getActivity())
Dmitri Plotnikov4292dfa2011-01-23 10:21:18 -0800276 .setIconAttribute(android.R.attr.alertDialogIcon)
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800277 .setMessage(messageId)
278 .setNegativeButton(android.R.string.cancel, null)
279 .setPositiveButton(android.R.string.ok,
280 new DialogInterface.OnClickListener() {
281 @Override
282 public void onClick(DialogInterface dialog, int whichButton) {
283 doDeleteContact(contactUri);
284 }
285 }
286 )
287 .create();
288
289 mDialog.setOnDismissListener(this);
290 mDialog.show();
291 }
292
293 @Override
294 public void onDismiss(DialogInterface dialog) {
295 mActive = false;
296 mDialog = null;
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800297 }
298
299 @Override
300 public void onSaveInstanceState(Bundle outState) {
301 super.onSaveInstanceState(outState);
302 outState.putBoolean(KEY_ACTIVE, mActive);
Dmitri Plotnikovcc42b452011-01-20 11:44:44 -0800303 outState.putParcelable(KEY_CONTACT_URI, mContactUri);
Dmitri Plotnikovc9eda372011-02-23 08:58:13 -0800304 outState.putBoolean(KEY_FINISH_WHEN_DONE, mFinishActivityWhenDone);
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800305 }
306
307 @Override
308 public void onActivityCreated(Bundle savedInstanceState) {
309 super.onActivityCreated(savedInstanceState);
310 if (savedInstanceState != null) {
311 mActive = savedInstanceState.getBoolean(KEY_ACTIVE);
Dmitri Plotnikovcc42b452011-01-20 11:44:44 -0800312 mContactUri = savedInstanceState.getParcelable(KEY_CONTACT_URI);
Dmitri Plotnikovc9eda372011-02-23 08:58:13 -0800313 mFinishActivityWhenDone = savedInstanceState.getBoolean(KEY_FINISH_WHEN_DONE);
Dmitri Plotnikov66bca8f2010-11-24 16:59:24 -0800314 }
315 }
316
317 protected void doDeleteContact(Uri contactUri) {
Dmitri Plotnikov7d8cabb2010-11-24 17:40:01 -0800318 mContext.startService(ContactSaveService.createDeleteContactIntent(mContext, contactUri));
Dmitri Plotnikovc9eda372011-02-23 08:58:13 -0800319 if (isAdded() && mFinishActivityWhenDone) {
320 getActivity().finish();
321 }
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700322 }
Dmitri Plotnikov9692f262010-06-29 18:53:11 -0700323}