• 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 import android.text.TextUtils;
36 import android.util.Log;
37 
38 import com.android.contacts.ContactSaveService;
39 import com.android.contacts.R;
40 import com.android.contacts.common.model.AccountTypeManager;
41 import com.android.contacts.common.model.account.AccountType;
42 import com.google.common.annotations.VisibleForTesting;
43 import com.google.common.collect.Sets;
44 
45 import java.util.HashSet;
46 
47 /**
48  * An interaction invoked to delete a contact.
49  */
50 public class ContactDeletionInteraction extends Fragment
51         implements LoaderCallbacks<Cursor>, OnDismissListener {
52 
53     private static final String TAG = "ContactDeletionInteraction";
54     private static final String FRAGMENT_TAG = "deleteContact";
55 
56     private static final String KEY_ACTIVE = "active";
57     private static final String KEY_CONTACT_URI = "contactUri";
58     private static final String KEY_FINISH_WHEN_DONE = "finishWhenDone";
59     public static final String ARG_CONTACT_URI = "contactUri";
60     public static final int RESULT_CODE_DELETED = 3;
61 
62     private static final String[] ENTITY_PROJECTION = new String[] {
63         Entity.RAW_CONTACT_ID, //0
64         Entity.ACCOUNT_TYPE, //1
65         Entity.DATA_SET, // 2
66         Entity.CONTACT_ID, // 3
67         Entity.LOOKUP_KEY, // 4
68     };
69 
70     private static final int COLUMN_INDEX_RAW_CONTACT_ID = 0;
71     private static final int COLUMN_INDEX_ACCOUNT_TYPE = 1;
72     private static final int COLUMN_INDEX_DATA_SET = 2;
73     private static final int COLUMN_INDEX_CONTACT_ID = 3;
74     private static final int COLUMN_INDEX_LOOKUP_KEY = 4;
75 
76     private boolean mActive;
77     private Uri mContactUri;
78     private boolean mFinishActivityWhenDone;
79     private Context mContext;
80     private AlertDialog mDialog;
81 
82     /** This is a wrapper around the fragment's loader manager to be used only during testing. */
83     private TestLoaderManagerBase mTestLoaderManager;
84 
85     @VisibleForTesting
86     int mMessageId;
87 
88     /**
89      * Starts the interaction.
90      *
91      * @param activity the activity within which to start the interaction
92      * @param contactUri the URI of the contact to delete
93      * @param finishActivityWhenDone whether to finish the activity upon completion of the
94      *        interaction
95      * @return the newly created interaction
96      */
start( Activity activity, Uri contactUri, boolean finishActivityWhenDone)97     public static ContactDeletionInteraction start(
98             Activity activity, Uri contactUri, boolean finishActivityWhenDone) {
99         return startWithTestLoaderManager(activity, contactUri, finishActivityWhenDone, null);
100     }
101 
102     /**
103      * Starts the interaction and optionally set up a {@link TestLoaderManagerBase}.
104      *
105      * @param activity the activity within which to start the interaction
106      * @param contactUri the URI of the contact to delete
107      * @param finishActivityWhenDone whether to finish the activity upon completion of the
108      *        interaction
109      * @param testLoaderManager the {@link TestLoaderManagerBase} to use to load the data, may be null
110      *        in which case the default {@link LoaderManager} is used
111      * @return the newly created interaction
112      */
113     @VisibleForTesting
startWithTestLoaderManager( Activity activity, Uri contactUri, boolean finishActivityWhenDone, TestLoaderManagerBase testLoaderManager)114     static ContactDeletionInteraction startWithTestLoaderManager(
115             Activity activity, Uri contactUri, boolean finishActivityWhenDone,
116             TestLoaderManagerBase testLoaderManager) {
117         if (contactUri == null) {
118             return null;
119         }
120 
121         FragmentManager fragmentManager = activity.getFragmentManager();
122         ContactDeletionInteraction fragment =
123                 (ContactDeletionInteraction) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
124         if (fragment == null) {
125             fragment = new ContactDeletionInteraction();
126             fragment.setTestLoaderManager(testLoaderManager);
127             fragment.setContactUri(contactUri);
128             fragment.setFinishActivityWhenDone(finishActivityWhenDone);
129             fragmentManager.beginTransaction().add(fragment, FRAGMENT_TAG)
130                     .commitAllowingStateLoss();
131         } else {
132             fragment.setTestLoaderManager(testLoaderManager);
133             fragment.setContactUri(contactUri);
134             fragment.setFinishActivityWhenDone(finishActivityWhenDone);
135         }
136         return fragment;
137     }
138 
139     @Override
getLoaderManager()140     public LoaderManager getLoaderManager() {
141         // Return the TestLoaderManager if one is set up.
142         LoaderManager loaderManager = super.getLoaderManager();
143         if (mTestLoaderManager != null) {
144             // Set the delegate: this operation is idempotent, so let's just do it every time.
145             mTestLoaderManager.setDelegate(loaderManager);
146             return mTestLoaderManager;
147         } else {
148             return loaderManager;
149         }
150     }
151 
152     /** Sets the TestLoaderManager that is used to wrap the actual LoaderManager in tests. */
setTestLoaderManager(TestLoaderManagerBase mockLoaderManager)153     private void setTestLoaderManager(TestLoaderManagerBase mockLoaderManager) {
154         mTestLoaderManager = mockLoaderManager;
155     }
156 
157     @Override
onAttach(Activity activity)158     public void onAttach(Activity activity) {
159         super.onAttach(activity);
160         mContext = activity;
161     }
162 
163     @Override
onDestroyView()164     public void onDestroyView() {
165         super.onDestroyView();
166         if (mDialog != null && mDialog.isShowing()) {
167             mDialog.setOnDismissListener(null);
168             mDialog.dismiss();
169             mDialog = null;
170         }
171     }
172 
setContactUri(Uri contactUri)173     public void setContactUri(Uri contactUri) {
174         mContactUri = contactUri;
175         mActive = true;
176         if (isStarted()) {
177             Bundle args = new Bundle();
178             args.putParcelable(ARG_CONTACT_URI, mContactUri);
179             getLoaderManager().restartLoader(R.id.dialog_delete_contact_loader_id, args, this);
180         }
181     }
182 
setFinishActivityWhenDone(boolean finishActivityWhenDone)183     private void setFinishActivityWhenDone(boolean finishActivityWhenDone) {
184         this.mFinishActivityWhenDone = finishActivityWhenDone;
185 
186     }
187 
188     /* Visible for testing */
isStarted()189     boolean isStarted() {
190         return isAdded();
191     }
192 
193     @Override
onStart()194     public void onStart() {
195         if (mActive) {
196             Bundle args = new Bundle();
197             args.putParcelable(ARG_CONTACT_URI, mContactUri);
198             getLoaderManager().initLoader(R.id.dialog_delete_contact_loader_id, args, this);
199         }
200         super.onStart();
201     }
202 
203     @Override
onStop()204     public void onStop() {
205         super.onStop();
206         if (mDialog != null) {
207             mDialog.hide();
208         }
209     }
210 
211     @Override
onCreateLoader(int id, Bundle args)212     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
213         Uri contactUri = args.getParcelable(ARG_CONTACT_URI);
214         return new CursorLoader(mContext,
215                 Uri.withAppendedPath(contactUri, Entity.CONTENT_DIRECTORY), ENTITY_PROJECTION,
216                 null, null, null);
217     }
218 
219     @Override
onLoadFinished(Loader<Cursor> loader, Cursor cursor)220     public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
221         if (mDialog != null) {
222             mDialog.dismiss();
223             mDialog = null;
224         }
225 
226         if (!mActive) {
227             return;
228         }
229 
230         if (cursor == null || cursor.isClosed()) {
231             Log.e(TAG, "Failed to load contacts");
232             return;
233         }
234 
235         long contactId = 0;
236         String lookupKey = null;
237 
238         // This cursor may contain duplicate raw contacts, so we need to de-dupe them first
239         HashSet<Long>  readOnlyRawContacts = Sets.newHashSet();
240         HashSet<Long>  writableRawContacts = Sets.newHashSet();
241 
242         AccountTypeManager accountTypes = AccountTypeManager.getInstance(getActivity());
243         cursor.moveToPosition(-1);
244         while (cursor.moveToNext()) {
245             final long rawContactId = cursor.getLong(COLUMN_INDEX_RAW_CONTACT_ID);
246             final String accountType = cursor.getString(COLUMN_INDEX_ACCOUNT_TYPE);
247             final String dataSet = cursor.getString(COLUMN_INDEX_DATA_SET);
248             contactId = cursor.getLong(COLUMN_INDEX_CONTACT_ID);
249             lookupKey = cursor.getString(COLUMN_INDEX_LOOKUP_KEY);
250             AccountType type = accountTypes.getAccountType(accountType, dataSet);
251             boolean writable = type == null || type.areContactsWritable();
252             if (writable) {
253                 writableRawContacts.add(rawContactId);
254             } else {
255                 readOnlyRawContacts.add(rawContactId);
256             }
257         }
258         if (TextUtils.isEmpty(lookupKey)) {
259             Log.e(TAG, "Failed to find contact lookup key");
260             getActivity().finish();
261             return;
262         }
263 
264         int readOnlyCount = readOnlyRawContacts.size();
265         int writableCount = writableRawContacts.size();
266         if (readOnlyCount > 0 && writableCount > 0) {
267             mMessageId = R.string.readOnlyContactDeleteConfirmation;
268         } else if (readOnlyCount > 0 && writableCount == 0) {
269             mMessageId = R.string.readOnlyContactWarning;
270         } else if (readOnlyCount == 0 && writableCount > 1) {
271             mMessageId = R.string.multipleContactDeleteConfirmation;
272         } else {
273             mMessageId = R.string.deleteConfirmation;
274         }
275 
276         final Uri contactUri = Contacts.getLookupUri(contactId, lookupKey);
277         showDialog(mMessageId, contactUri);
278 
279         // We don't want onLoadFinished() calls any more, which may come when the database is
280         // updating.
281         getLoaderManager().destroyLoader(R.id.dialog_delete_contact_loader_id);
282     }
283 
284     @Override
onLoaderReset(Loader<Cursor> loader)285     public void onLoaderReset(Loader<Cursor> loader) {
286     }
287 
showDialog(int messageId, final Uri contactUri)288     private void showDialog(int messageId, final Uri contactUri) {
289         mDialog = new AlertDialog.Builder(getActivity())
290                 .setIconAttribute(android.R.attr.alertDialogIcon)
291                 .setMessage(messageId)
292                 .setNegativeButton(android.R.string.cancel, null)
293                 .setPositiveButton(android.R.string.ok,
294                     new DialogInterface.OnClickListener() {
295                         @Override
296                         public void onClick(DialogInterface dialog, int whichButton) {
297                             doDeleteContact(contactUri);
298                         }
299                     }
300                 )
301                 .create();
302 
303         mDialog.setOnDismissListener(this);
304         mDialog.show();
305     }
306 
307     @Override
onDismiss(DialogInterface dialog)308     public void onDismiss(DialogInterface dialog) {
309         mActive = false;
310         mDialog = null;
311     }
312 
313     @Override
onSaveInstanceState(Bundle outState)314     public void onSaveInstanceState(Bundle outState) {
315         super.onSaveInstanceState(outState);
316         outState.putBoolean(KEY_ACTIVE, mActive);
317         outState.putParcelable(KEY_CONTACT_URI, mContactUri);
318         outState.putBoolean(KEY_FINISH_WHEN_DONE, mFinishActivityWhenDone);
319     }
320 
321     @Override
onActivityCreated(Bundle savedInstanceState)322     public void onActivityCreated(Bundle savedInstanceState) {
323         super.onActivityCreated(savedInstanceState);
324         if (savedInstanceState != null) {
325             mActive = savedInstanceState.getBoolean(KEY_ACTIVE);
326             mContactUri = savedInstanceState.getParcelable(KEY_CONTACT_URI);
327             mFinishActivityWhenDone = savedInstanceState.getBoolean(KEY_FINISH_WHEN_DONE);
328         }
329     }
330 
doDeleteContact(Uri contactUri)331     protected void doDeleteContact(Uri contactUri) {
332         mContext.startService(ContactSaveService.createDeleteContactIntent(mContext, contactUri));
333         if (isAdded() && mFinishActivityWhenDone) {
334             getActivity().setResult(RESULT_CODE_DELETED);
335             getActivity().finish();
336         }
337     }
338 }
339