1package com.android.contacts.activities;
2
3import android.app.Activity;
4import android.app.FragmentManager;
5import android.app.FragmentTransaction;
6import android.app.LoaderManager;
7import android.content.ContentUris;
8import android.content.Intent;
9import android.content.Loader;
10import android.net.Uri;
11import android.os.Bundle;
12import android.provider.ContactsContract;
13import android.provider.ContactsContract.RawContacts;
14import android.widget.Toast;
15
16import com.android.contacts.AppCompatContactsActivity;
17import com.android.contacts.ContactSaveService;
18import com.android.contacts.R;
19import com.android.contacts.editor.ContactEditorFragment;
20import com.android.contacts.editor.EditorIntents;
21import com.android.contacts.editor.PickRawContactDialogFragment;
22import com.android.contacts.editor.PickRawContactLoader;
23import com.android.contacts.editor.PickRawContactLoader.RawContactsMetadata;
24import com.android.contacts.editor.SplitContactConfirmationDialogFragment;
25import com.android.contacts.logging.EditorEvent;
26import com.android.contacts.logging.Logger;
27import com.android.contacts.model.AccountTypeManager;
28import com.android.contacts.quickcontact.QuickContactActivity;
29import com.android.contacts.util.ImplicitIntentsUtil;
30import com.android.contacts.util.MaterialColorMapUtils.MaterialPalette;
31import com.android.contactsbind.FeedbackHelper;
32
33/**
34 * Transparent springboard activity that hosts a dialog to select a raw contact to edit.
35 * All intents coming out from this activity have {@code FLAG_ACTIVITY_FORWARD_RESULT} set.
36 */
37public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity implements
38        PickRawContactDialogFragment.PickRawContactListener,
39        SplitContactConfirmationDialogFragment.Listener {
40
41    private static final String TAG = "EditorSpringBoard";
42    private static final String TAG_RAW_CONTACTS_DIALOG = "rawContactsDialog";
43    private static final String KEY_RAW_CONTACTS_METADATA = "rawContactsMetadata";
44    private static final int LOADER_RAW_CONTACTS = 1;
45
46    public static final String EXTRA_SHOW_READ_ONLY = "showReadOnly";
47
48    private Uri mUri;
49    private RawContactsMetadata mResult;
50    private MaterialPalette mMaterialPalette;
51    private boolean mHasWritableAccount;
52    private boolean mShowReadOnly;
53    private int mWritableAccountPosition;
54
55    /**
56     * The contact data loader listener.
57     */
58    protected final LoaderManager.LoaderCallbacks<RawContactsMetadata> mRawContactLoaderListener =
59            new LoaderManager.LoaderCallbacks<RawContactsMetadata>() {
60
61                @Override
62                public Loader<RawContactsMetadata> onCreateLoader(int id, Bundle args) {
63                    return new PickRawContactLoader(ContactEditorSpringBoardActivity.this, mUri);
64                }
65
66                @Override
67                public void onLoadFinished(Loader<RawContactsMetadata> loader,
68                        RawContactsMetadata result) {
69                    if (result == null) {
70                        toastErrorAndFinish();
71                        return;
72                    }
73                    mResult = result;
74                    onLoad();
75                }
76
77                @Override
78                public void onLoaderReset(Loader<RawContactsMetadata> loader) {
79                }
80            };
81
82
83    @Override
84    protected void onCreate(Bundle savedInstanceState) {
85        super.onCreate(savedInstanceState);
86
87        if (RequestPermissionsActivity.startPermissionActivityIfNeeded(this)) {
88            return;
89        }
90
91        final Intent intent = getIntent();
92        final String action = intent.getAction();
93
94        if (!Intent.ACTION_EDIT.equals(action)) {
95            finish();
96            return;
97        }
98        // Just for shorter variable names.
99        final String primary = ContactEditorFragment.INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR;
100        final String secondary =
101                ContactEditorFragment.INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR;
102        if (intent.hasExtra(primary) && intent.hasExtra(secondary)) {
103            mMaterialPalette = new MaterialPalette(intent.getIntExtra(primary, -1),
104                    intent.getIntExtra(secondary, -1));
105        }
106        mShowReadOnly = intent.getBooleanExtra(EXTRA_SHOW_READ_ONLY, false);
107
108        mUri = intent.getData();
109        final String authority = mUri.getAuthority();
110        final String type = getContentResolver().getType(mUri);
111        // Go straight to editor if we're passed a raw contact Uri.
112        if (ContactsContract.AUTHORITY.equals(authority) &&
113                RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
114            Logger.logEditorEvent(
115                    EditorEvent.EventType.SHOW_RAW_CONTACT_PICKER, /* numberRawContacts */ 0);
116            final long rawContactId = ContentUris.parseId(mUri);
117            startEditorAndForwardExtras(getIntentForRawContact(rawContactId));
118        } else if (android.provider.Contacts.AUTHORITY.equals(authority)) {
119            // Fail if given a legacy URI.
120            FeedbackHelper.sendFeedback(this, TAG,
121                    "Legacy Uri was passed to editor.", new IllegalArgumentException());
122            toastErrorAndFinish();
123        } else {
124            getLoaderManager().initLoader(LOADER_RAW_CONTACTS, null, mRawContactLoaderListener);
125        }
126    }
127
128    @Override
129    public void onPickRawContact(long rawContactId) {
130        startEditorAndForwardExtras(getIntentForRawContact(rawContactId));
131    }
132
133    /**
134     * Once the load is finished, decide whether to show the dialog or load the editor directly.
135     */
136    private void onLoad() {
137        maybeTrimReadOnly();
138        setHasWritableAccount();
139        if (mShowReadOnly || (mResult.rawContacts.size() > 1 && mHasWritableAccount)) {
140            showDialog();
141        } else {
142            loadEditor();
143        }
144    }
145
146    /**
147     * If not configured to show read only raw contact, trim them from the result.
148     */
149    private void maybeTrimReadOnly() {
150        mResult.showReadOnly = mShowReadOnly;
151        if (mShowReadOnly) {
152            return;
153        }
154
155        mResult.trimReadOnly(AccountTypeManager.getInstance(this));
156    }
157
158    /**
159     * Start the dialog to pick the raw contact to edit.
160     */
161    private void showDialog() {
162        final FragmentManager fm = getFragmentManager();
163        final SplitContactConfirmationDialogFragment split =
164                (SplitContactConfirmationDialogFragment) fm
165                        .findFragmentByTag(SplitContactConfirmationDialogFragment.TAG);
166        // If we were showing the split confirmation before show it again.
167        if (split != null && split.isAdded()) {
168            fm.beginTransaction().show(split).commitAllowingStateLoss();
169            return;
170        }
171        PickRawContactDialogFragment pick = (PickRawContactDialogFragment) fm
172                .findFragmentByTag(TAG_RAW_CONTACTS_DIALOG);
173        if (pick == null) {
174            pick = PickRawContactDialogFragment.getInstance(mResult);
175            final FragmentTransaction ft = fm.beginTransaction();
176            ft.add(pick, TAG_RAW_CONTACTS_DIALOG);
177            // commitAllowingStateLoss is safe in this activity because the fragment entirely
178            // depends on the result of the loader. Even if we lose the fragment because the
179            // activity was in the background, when it comes back onLoadFinished will be called
180            // again which will have all the state the picker needs. This situation should be
181            // very rare, since the load should be quick.
182            ft.commitAllowingStateLoss();
183        }
184    }
185
186    /**
187     * Starts the editor for the only writable raw contact in the cursor if one exists. Otherwise,
188     * the editor is started normally and handles creation of a new writable raw contact.
189     */
190    private void loadEditor() {
191        Logger.logEditorEvent(
192                EditorEvent.EventType.SHOW_RAW_CONTACT_PICKER, /* numberRawContacts */ 0);
193        final Intent intent;
194        if (mHasWritableAccount) {
195            intent = getIntentForRawContact(mResult.rawContacts.get(mWritableAccountPosition).id);
196        } else {
197            // If the contact has only read-only raw contacts, we'll want to let the editor create
198            // the writable raw contact for it.
199            intent = EditorIntents.createEditContactIntent(this, mUri, mMaterialPalette, -1);
200            intent.setClass(this, ContactEditorActivity.class);
201        }
202        startEditorAndForwardExtras(intent);
203    }
204
205    /**
206     * Determines if this contact has a writable account.
207     */
208    private void setHasWritableAccount() {
209        mWritableAccountPosition = mResult.getIndexOfFirstWritableAccount(
210                AccountTypeManager.getInstance(this));
211        mHasWritableAccount = mWritableAccountPosition != -1;
212    }
213
214    /**
215     * Returns an intent to load the editor for the given raw contact. Sets
216     * {@code FLAG_ACTIVITY_FORWARD_RESULT} in case the activity that started us expects a result.
217     * @param rawContactId Raw contact to edit
218     */
219    private Intent getIntentForRawContact(long rawContactId) {
220        final Intent intent = EditorIntents.createEditContactIntentForRawContact(
221                this, mUri, rawContactId, mMaterialPalette);
222        intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
223        return intent;
224    }
225
226    /**
227     * Starts the given intent within the app, attaching any extras to it that were passed to us.
228     */
229    private void startEditorAndForwardExtras(Intent intent) {
230        final Bundle extras = getIntent().getExtras();
231        if (extras != null) {
232            intent.putExtras(extras);
233        }
234        ImplicitIntentsUtil.startActivityInApp(this, intent);
235        finish();
236    }
237
238    private void toastErrorAndFinish() {
239        Toast.makeText(ContactEditorSpringBoardActivity.this,
240                R.string.editor_failed_to_load, Toast.LENGTH_SHORT).show();
241        setResult(RESULT_CANCELED, null);
242        finish();
243    }
244
245    @Override
246    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
247        // Ignore failed requests
248        if (resultCode != Activity.RESULT_OK) {
249            finish();
250        }
251        if (data != null) {
252            final Intent intent = ContactSaveService.createJoinContactsIntent(
253                    this, mResult.contactId, ContentUris.parseId(data.getData()),
254                    QuickContactActivity.class, Intent.ACTION_VIEW);
255            startService(intent);
256            finish();
257        }
258    }
259
260    @Override
261    public void onSplitContactConfirmed(boolean hasPendingChanges) {
262        final long[][] rawContactIds = getRawContactIds();
263        final Intent intent = ContactSaveService.createHardSplitContactIntent(this, rawContactIds);
264        startService(intent);
265        finish();
266    }
267
268    @Override
269    public void onSplitContactCanceled() {
270        finish();
271    }
272
273    @Override
274    protected void onSaveInstanceState(Bundle outState) {
275        super.onSaveInstanceState(outState);
276        outState.putParcelable(KEY_RAW_CONTACTS_METADATA, mResult);
277    }
278
279    @Override
280    protected void onRestoreInstanceState(Bundle savedInstanceState) {
281        super.onRestoreInstanceState(savedInstanceState);
282        mResult = savedInstanceState.getParcelable(KEY_RAW_CONTACTS_METADATA);
283    }
284
285    private long[][] getRawContactIds() {
286        final long[][] result = new long[mResult.rawContacts.size()][1];
287        for (int i = 0; i < mResult.rawContacts.size(); i++) {
288            result[i][0] = mResult.rawContacts.get(i).id;
289        }
290        return result;
291    }
292}
293