1 package com.android.contacts.activities;
2 
3 import android.app.Activity;
4 import android.app.FragmentManager;
5 import android.app.FragmentTransaction;
6 import android.app.LoaderManager;
7 import android.content.ContentUris;
8 import android.content.Intent;
9 import android.content.Loader;
10 import android.net.Uri;
11 import android.os.Bundle;
12 import android.provider.ContactsContract;
13 import android.provider.ContactsContract.RawContacts;
14 import android.widget.Toast;
15 
16 import com.android.contacts.AppCompatContactsActivity;
17 import com.android.contacts.ContactSaveService;
18 import com.android.contacts.R;
19 import com.android.contacts.editor.ContactEditorFragment;
20 import com.android.contacts.editor.EditorIntents;
21 import com.android.contacts.editor.PickRawContactDialogFragment;
22 import com.android.contacts.editor.PickRawContactLoader;
23 import com.android.contacts.editor.PickRawContactLoader.RawContactsMetadata;
24 import com.android.contacts.editor.SplitContactConfirmationDialogFragment;
25 import com.android.contacts.logging.EditorEvent;
26 import com.android.contacts.logging.Logger;
27 import com.android.contacts.model.AccountTypeManager;
28 import com.android.contacts.quickcontact.QuickContactActivity;
29 import com.android.contacts.util.ImplicitIntentsUtil;
30 import com.android.contacts.util.MaterialColorMapUtils.MaterialPalette;
31 import 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  */
37 public 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
onCreate(Bundle savedInstanceState)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
onPickRawContact(long rawContactId)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      */
onLoad()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      */
maybeTrimReadOnly()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      */
showDialog()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      */
loadEditor()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      */
setHasWritableAccount()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      */
getIntentForRawContact(long rawContactId)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      */
startEditorAndForwardExtras(Intent intent)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 
toastErrorAndFinish()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
onActivityResult(int requestCode, int resultCode, Intent data)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
onSplitContactConfirmed(boolean hasPendingChanges)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
onSplitContactCanceled()269     public void onSplitContactCanceled() {
270         finish();
271     }
272 
273     @Override
onSaveInstanceState(Bundle outState)274     protected void onSaveInstanceState(Bundle outState) {
275         super.onSaveInstanceState(outState);
276         outState.putParcelable(KEY_RAW_CONTACTS_METADATA, mResult);
277     }
278 
279     @Override
onRestoreInstanceState(Bundle savedInstanceState)280     protected void onRestoreInstanceState(Bundle savedInstanceState) {
281         super.onRestoreInstanceState(savedInstanceState);
282         mResult = savedInstanceState.getParcelable(KEY_RAW_CONTACTS_METADATA);
283     }
284 
getRawContactIds()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