ContactDeletionInteraction.java revision 2b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbc
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.android.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).commit();
127        } else {
128            fragment.setTestLoaderManager(testLoaderManager);
129            fragment.setContactUri(contactUri);
130            fragment.setFinishActivityWhenDone(finishActivityWhenDone);
131        }
132        return fragment;
133    }
134
135    @Override
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. */
149    private void setTestLoaderManager(TestLoaderManager mockLoaderManager) {
150        mTestLoaderManager = mockLoaderManager;
151    }
152
153    @Override
154    public void onAttach(Activity activity) {
155        super.onAttach(activity);
156        mContext = activity;
157    }
158
159    @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
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
179    private void setFinishActivityWhenDone(boolean finishActivityWhenDone) {
180        this.mFinishActivityWhenDone = finishActivityWhenDone;
181
182    }
183
184    /* 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);
194            getLoaderManager().initLoader(R.id.dialog_delete_contact_loader_id, args, this);
195        }
196        super.onStart();
197    }
198
199    @Override
200    public void onStop() {
201        super.onStop();
202        if (mDialog != null) {
203            mDialog.hide();
204        }
205    }
206
207    @Override
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
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 readonly = type != null && type.readOnly;
243            if (readonly) {
244                readOnlyRawContacts.add(rawContactId);
245            } else {
246                writableRawContacts.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
271    public void onLoaderReset(Loader<Cursor> loader) {
272    }
273
274    private void showDialog(int messageId, final Uri contactUri) {
275        mDialog = new AlertDialog.Builder(getActivity())
276                .setTitle(R.string.deleteConfirmation_title)
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