package com.android.contacts.activities; import android.app.Activity; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.app.LoaderManager; import android.content.ContentUris; import android.content.Intent; import android.content.Loader; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract; import android.provider.ContactsContract.RawContacts; import android.widget.Toast; import com.android.contacts.AppCompatContactsActivity; import com.android.contacts.ContactSaveService; import com.android.contacts.R; import com.android.contacts.editor.ContactEditorFragment; import com.android.contacts.editor.EditorIntents; import com.android.contacts.editor.PickRawContactDialogFragment; import com.android.contacts.editor.PickRawContactLoader; import com.android.contacts.editor.PickRawContactLoader.RawContactsMetadata; import com.android.contacts.editor.SplitContactConfirmationDialogFragment; import com.android.contacts.logging.EditorEvent; import com.android.contacts.logging.Logger; import com.android.contacts.model.AccountTypeManager; import com.android.contacts.quickcontact.QuickContactActivity; import com.android.contacts.util.ImplicitIntentsUtil; import com.android.contacts.util.MaterialColorMapUtils.MaterialPalette; import com.android.contactsbind.FeedbackHelper; /** * Transparent springboard activity that hosts a dialog to select a raw contact to edit. * All intents coming out from this activity have {@code FLAG_ACTIVITY_FORWARD_RESULT} set. */ public class ContactEditorSpringBoardActivity extends AppCompatContactsActivity implements PickRawContactDialogFragment.PickRawContactListener, SplitContactConfirmationDialogFragment.Listener { private static final String TAG = "EditorSpringBoard"; private static final String TAG_RAW_CONTACTS_DIALOG = "rawContactsDialog"; private static final String KEY_RAW_CONTACTS_METADATA = "rawContactsMetadata"; private static final int LOADER_RAW_CONTACTS = 1; public static final String EXTRA_SHOW_READ_ONLY = "showReadOnly"; private Uri mUri; private RawContactsMetadata mResult; private MaterialPalette mMaterialPalette; private boolean mHasWritableAccount; private boolean mShowReadOnly; private int mWritableAccountPosition; /** * The contact data loader listener. */ protected final LoaderManager.LoaderCallbacks mRawContactLoaderListener = new LoaderManager.LoaderCallbacks() { @Override public Loader onCreateLoader(int id, Bundle args) { return new PickRawContactLoader(ContactEditorSpringBoardActivity.this, mUri); } @Override public void onLoadFinished(Loader loader, RawContactsMetadata result) { if (result == null) { toastErrorAndFinish(); return; } mResult = result; onLoad(); } @Override public void onLoaderReset(Loader loader) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (RequestPermissionsActivity.startPermissionActivityIfNeeded(this)) { return; } final Intent intent = getIntent(); final String action = intent.getAction(); if (!Intent.ACTION_EDIT.equals(action)) { finish(); return; } // Just for shorter variable names. final String primary = ContactEditorFragment.INTENT_EXTRA_MATERIAL_PALETTE_PRIMARY_COLOR; final String secondary = ContactEditorFragment.INTENT_EXTRA_MATERIAL_PALETTE_SECONDARY_COLOR; if (intent.hasExtra(primary) && intent.hasExtra(secondary)) { mMaterialPalette = new MaterialPalette(intent.getIntExtra(primary, -1), intent.getIntExtra(secondary, -1)); } mShowReadOnly = intent.getBooleanExtra(EXTRA_SHOW_READ_ONLY, false); mUri = intent.getData(); final String authority = mUri.getAuthority(); final String type = getContentResolver().getType(mUri); // Go straight to editor if we're passed a raw contact Uri. if (ContactsContract.AUTHORITY.equals(authority) && RawContacts.CONTENT_ITEM_TYPE.equals(type)) { Logger.logEditorEvent( EditorEvent.EventType.SHOW_RAW_CONTACT_PICKER, /* numberRawContacts */ 0); final long rawContactId = ContentUris.parseId(mUri); startEditorAndForwardExtras(getIntentForRawContact(rawContactId)); } else if (android.provider.Contacts.AUTHORITY.equals(authority)) { // Fail if given a legacy URI. FeedbackHelper.sendFeedback(this, TAG, "Legacy Uri was passed to editor.", new IllegalArgumentException()); toastErrorAndFinish(); } else { getLoaderManager().initLoader(LOADER_RAW_CONTACTS, null, mRawContactLoaderListener); } } @Override public void onPickRawContact(long rawContactId) { startEditorAndForwardExtras(getIntentForRawContact(rawContactId)); } /** * Once the load is finished, decide whether to show the dialog or load the editor directly. */ private void onLoad() { maybeTrimReadOnly(); setHasWritableAccount(); if (mShowReadOnly || (mResult.rawContacts.size() > 1 && mHasWritableAccount)) { showDialog(); } else { loadEditor(); } } /** * If not configured to show read only raw contact, trim them from the result. */ private void maybeTrimReadOnly() { mResult.showReadOnly = mShowReadOnly; if (mShowReadOnly) { return; } mResult.trimReadOnly(AccountTypeManager.getInstance(this)); } /** * Start the dialog to pick the raw contact to edit. */ private void showDialog() { final FragmentManager fm = getFragmentManager(); final SplitContactConfirmationDialogFragment split = (SplitContactConfirmationDialogFragment) fm .findFragmentByTag(SplitContactConfirmationDialogFragment.TAG); // If we were showing the split confirmation before show it again. if (split != null && split.isAdded()) { fm.beginTransaction().show(split).commitAllowingStateLoss(); return; } PickRawContactDialogFragment pick = (PickRawContactDialogFragment) fm .findFragmentByTag(TAG_RAW_CONTACTS_DIALOG); if (pick == null) { pick = PickRawContactDialogFragment.getInstance(mResult); final FragmentTransaction ft = fm.beginTransaction(); ft.add(pick, TAG_RAW_CONTACTS_DIALOG); // commitAllowingStateLoss is safe in this activity because the fragment entirely // depends on the result of the loader. Even if we lose the fragment because the // activity was in the background, when it comes back onLoadFinished will be called // again which will have all the state the picker needs. This situation should be // very rare, since the load should be quick. ft.commitAllowingStateLoss(); } } /** * Starts the editor for the only writable raw contact in the cursor if one exists. Otherwise, * the editor is started normally and handles creation of a new writable raw contact. */ private void loadEditor() { Logger.logEditorEvent( EditorEvent.EventType.SHOW_RAW_CONTACT_PICKER, /* numberRawContacts */ 0); final Intent intent; if (mHasWritableAccount) { intent = getIntentForRawContact(mResult.rawContacts.get(mWritableAccountPosition).id); } else { // If the contact has only read-only raw contacts, we'll want to let the editor create // the writable raw contact for it. intent = EditorIntents.createEditContactIntent(this, mUri, mMaterialPalette, -1); intent.setClass(this, ContactEditorActivity.class); } startEditorAndForwardExtras(intent); } /** * Determines if this contact has a writable account. */ private void setHasWritableAccount() { mWritableAccountPosition = mResult.getIndexOfFirstWritableAccount( AccountTypeManager.getInstance(this)); mHasWritableAccount = mWritableAccountPosition != -1; } /** * Returns an intent to load the editor for the given raw contact. Sets * {@code FLAG_ACTIVITY_FORWARD_RESULT} in case the activity that started us expects a result. * @param rawContactId Raw contact to edit */ private Intent getIntentForRawContact(long rawContactId) { final Intent intent = EditorIntents.createEditContactIntentForRawContact( this, mUri, rawContactId, mMaterialPalette); intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); return intent; } /** * Starts the given intent within the app, attaching any extras to it that were passed to us. */ private void startEditorAndForwardExtras(Intent intent) { final Bundle extras = getIntent().getExtras(); if (extras != null) { intent.putExtras(extras); } ImplicitIntentsUtil.startActivityInApp(this, intent); finish(); } private void toastErrorAndFinish() { Toast.makeText(ContactEditorSpringBoardActivity.this, R.string.editor_failed_to_load, Toast.LENGTH_SHORT).show(); setResult(RESULT_CANCELED, null); finish(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // Ignore failed requests if (resultCode != Activity.RESULT_OK) { finish(); } if (data != null) { final Intent intent = ContactSaveService.createJoinContactsIntent( this, mResult.contactId, ContentUris.parseId(data.getData()), QuickContactActivity.class, Intent.ACTION_VIEW); startService(intent); finish(); } } @Override public void onSplitContactConfirmed(boolean hasPendingChanges) { final long[][] rawContactIds = getRawContactIds(); final Intent intent = ContactSaveService.createHardSplitContactIntent(this, rawContactIds); startService(intent); finish(); } @Override public void onSplitContactCanceled() { finish(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable(KEY_RAW_CONTACTS_METADATA, mResult); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mResult = savedInstanceState.getParcelable(KEY_RAW_CONTACTS_METADATA); } private long[][] getRawContactIds() { final long[][] result = new long[mResult.rawContacts.size()][1]; for (int i = 0; i < mResult.rawContacts.size(); i++) { result[i][0] = mResult.rawContacts.get(i).id; } return result; } }