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