• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
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;
24 import android.app.LoaderManager.LoaderCallbacks;
25 import android.content.Context;
26 import android.content.CursorLoader;
27 import android.content.DialogInterface;
28 import android.content.DialogInterface.OnDismissListener;
29 import android.content.Loader;
30 import android.database.Cursor;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.provider.ContactsContract.Contacts;
34 import android.provider.ContactsContract.Contacts.Entity;
35 
36 import com.android.contacts.ContactSaveService;
37 import com.android.contacts.R;
38 import com.android.contacts.model.AccountTypeManager;
39 import com.android.contacts.model.account.AccountType;
40 import com.google.common.annotations.VisibleForTesting;
41 import com.google.common.collect.Sets;
42 
43 import java.util.HashSet;
44 
45 /**
46  * An interaction invoked to delete a contact.
47  */
48 public class ContactDeletionInteraction extends Fragment
49         implements LoaderCallbacks<Cursor>, OnDismissListener {
50 
51     private static final String FRAGMENT_TAG = "deleteContact";
52 
53     private static final String KEY_ACTIVE = "active";
54     private static final String KEY_CONTACT_URI = "contactUri";
55     private static final String KEY_FINISH_WHEN_DONE = "finishWhenDone";
56     public static final String ARG_CONTACT_URI = "contactUri";
57 
58     private static final String[] ENTITY_PROJECTION = new String[] {
59         Entity.RAW_CONTACT_ID, //0
60         Entity.ACCOUNT_TYPE, //1
61         Entity.DATA_SET, // 2
62         Entity.CONTACT_ID, // 3
63         Entity.LOOKUP_KEY, // 4
64     };
65 
66     private static final int COLUMN_INDEX_RAW_CONTACT_ID = 0;
67     private static final int COLUMN_INDEX_ACCOUNT_TYPE = 1;
68     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;
71 
72     private boolean mActive;
73     private Uri mContactUri;
74     private boolean mFinishActivityWhenDone;
75     private Context mContext;
76     private AlertDialog mDialog;
77 
78     /** This is a wrapper around the fragment's loader manager to be used only during testing. */
79     private TestLoaderManager mTestLoaderManager;
80 
81     @VisibleForTesting
82     int mMessageId;
83 
84     /**
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      */
start( Activity activity, Uri contactUri, boolean finishActivityWhenDone)93     public static ContactDeletionInteraction start(
94             Activity activity, Uri contactUri, boolean finishActivityWhenDone) {
95         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
startWithTestLoaderManager( Activity activity, Uri contactUri, boolean finishActivityWhenDone, TestLoaderManager testLoaderManager)110     static ContactDeletionInteraction startWithTestLoaderManager(
111             Activity activity, Uri contactUri, boolean finishActivityWhenDone,
112             TestLoaderManager testLoaderManager) {
113         if (contactUri == null) {
114             return null;
115         }
116 
117         FragmentManager fragmentManager = activity.getFragmentManager();
118         ContactDeletionInteraction fragment =
119                 (ContactDeletionInteraction) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
120         if (fragment == null) {
121             fragment = new ContactDeletionInteraction();
122             fragment.setTestLoaderManager(testLoaderManager);
123             fragment.setContactUri(contactUri);
124             fragment.setFinishActivityWhenDone(finishActivityWhenDone);
125             fragmentManager.beginTransaction().add(fragment, FRAGMENT_TAG)
126                     .commitAllowingStateLoss();
127         } else {
128             fragment.setTestLoaderManager(testLoaderManager);
129             fragment.setContactUri(contactUri);
130             fragment.setFinishActivityWhenDone(finishActivityWhenDone);
131         }
132         return fragment;
133     }
134 
135     @Override
getLoaderManager()136     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. */
setTestLoaderManager(TestLoaderManager mockLoaderManager)149     private void setTestLoaderManager(TestLoaderManager mockLoaderManager) {
150         mTestLoaderManager = mockLoaderManager;
151     }
152 
153     @Override
onAttach(Activity activity)154     public void onAttach(Activity activity) {
155         super.onAttach(activity);
156         mContext = activity;
157     }
158 
159     @Override
onDestroyView()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 
setContactUri(Uri contactUri)169     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);
175             getLoaderManager().restartLoader(R.id.dialog_delete_contact_loader_id, args, this);
176         }
177     }
178 
setFinishActivityWhenDone(boolean finishActivityWhenDone)179     private void setFinishActivityWhenDone(boolean finishActivityWhenDone) {
180         this.mFinishActivityWhenDone = finishActivityWhenDone;
181 
182     }
183 
184     /* Visible for testing */
isStarted()185     boolean isStarted() {
186         return isAdded();
187     }
188 
189     @Override
onStart()190     public void onStart() {
191         if (mActive) {
192             Bundle args = new Bundle();
193             args.putParcelable(ARG_CONTACT_URI, mContactUri);
194             getLoaderManager().initLoader(R.id.dialog_delete_contact_loader_id, args, this);
195         }
196         super.onStart();
197     }
198 
199     @Override
onStop()200     public void onStop() {
201         super.onStop();
202         if (mDialog != null) {
203             mDialog.hide();
204         }
205     }
206 
207     @Override
onCreateLoader(int id, Bundle args)208     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
onLoadFinished(Loader<Cursor> loader, Cursor cursor)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 
226         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();
232 
233         AccountTypeManager accountTypes = AccountTypeManager.getInstance(getActivity());
234         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);
238             final String dataSet = cursor.getString(COLUMN_INDEX_DATA_SET);
239             contactId = cursor.getLong(COLUMN_INDEX_CONTACT_ID);
240             lookupKey = cursor.getString(COLUMN_INDEX_LOOKUP_KEY);
241             AccountType type = accountTypes.getAccountType(accountType, dataSet);
242             boolean writable = type == null || type.areContactsWritable();
243             if (writable) {
244                 writableRawContacts.add(rawContactId);
245             } else {
246                 readOnlyRawContacts.add(rawContactId);
247             }
248         }
249 
250         int readOnlyCount = readOnlyRawContacts.size();
251         int writableCount = writableRawContacts.size();
252         if (readOnlyCount > 0 && writableCount > 0) {
253             mMessageId = R.string.readOnlyContactDeleteConfirmation;
254         } else if (readOnlyCount > 0 && writableCount == 0) {
255             mMessageId = R.string.readOnlyContactWarning;
256         } else if (readOnlyCount == 0 && writableCount > 1) {
257             mMessageId = R.string.multipleContactDeleteConfirmation;
258         } else {
259             mMessageId = R.string.deleteConfirmation;
260         }
261 
262         final Uri contactUri = Contacts.getLookupUri(contactId, lookupKey);
263         showDialog(mMessageId, contactUri);
264 
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);
268     }
269 
270     @Override
onLoaderReset(Loader<Cursor> loader)271     public void onLoaderReset(Loader<Cursor> loader) {
272     }
273 
showDialog(int messageId, final Uri contactUri)274     private void showDialog(int messageId, final Uri contactUri) {
275         mDialog = new AlertDialog.Builder(getActivity())
276                 .setIconAttribute(android.R.attr.alertDialogIcon)
277                 .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
onDismiss(DialogInterface dialog)294     public void onDismiss(DialogInterface dialog) {
295         mActive = false;
296         mDialog = null;
297     }
298 
299     @Override
onSaveInstanceState(Bundle outState)300     public void onSaveInstanceState(Bundle outState) {
301         super.onSaveInstanceState(outState);
302         outState.putBoolean(KEY_ACTIVE, mActive);
303         outState.putParcelable(KEY_CONTACT_URI, mContactUri);
304         outState.putBoolean(KEY_FINISH_WHEN_DONE, mFinishActivityWhenDone);
305     }
306 
307     @Override
onActivityCreated(Bundle savedInstanceState)308     public void onActivityCreated(Bundle savedInstanceState) {
309         super.onActivityCreated(savedInstanceState);
310         if (savedInstanceState != null) {
311             mActive = savedInstanceState.getBoolean(KEY_ACTIVE);
312             mContactUri = savedInstanceState.getParcelable(KEY_CONTACT_URI);
313             mFinishActivityWhenDone = savedInstanceState.getBoolean(KEY_FINISH_WHEN_DONE);
314         }
315     }
316 
doDeleteContact(Uri contactUri)317     protected void doDeleteContact(Uri contactUri) {
318         mContext.startService(ContactSaveService.createDeleteContactIntent(mContext, contactUri));
319         if (isAdded() && mFinishActivityWhenDone) {
320             getActivity().finish();
321         }
322     }
323 }
324