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