Move data loading and account creation to edit contact base class
While moving things from ContactEditorFragment to ContactEditorBaseFragment:
* The data loader callbacks, #onActivityCreated, #setData, and account
creation methods were not changed in any way
* For the restoration of contact editor state, one small change was made.
A new key, KEY_AUTO_ADD_TO_DEFAULT_GROUP, was added because the other
options passed in via intentExtras argument to #load are saved
so this looks like an oversight in the existing version.
Bug 19124091
Change-Id: I7973f9c5703082f6d8db53c9db22d08a230c5876
diff --git a/src/com/android/contacts/editor/CompactContactEditorFragment.java b/src/com/android/contacts/editor/CompactContactEditorFragment.java
index 5361155..46f50d2 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -16,15 +16,19 @@
package com.android.contacts.editor;
+import com.google.common.collect.ImmutableList;
+
import com.android.contacts.R;
import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor;
-import com.android.contacts.editor.ContactEditorBaseFragment.Listener;
+import com.android.contacts.common.model.Contact;
+import com.android.contacts.common.model.RawContact;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
-import android.app.Activity;
-import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -36,21 +40,6 @@
public class CompactContactEditorFragment extends ContactEditorBaseFragment
implements ContactEditor {
- private Context mContext;
- private Listener mListener;
-
- private String mAction;
- private Uri mLookupUri;
- private Bundle mIntentExtras;
-
- private LinearLayout mContent;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mContext = activity;
- }
-
@Override
public void setListener(Listener listener) {
mListener = listener;
@@ -64,6 +53,36 @@
return view;
}
+ //
+ // ContactEditorBaseFragment
+ //
+
+ @Override
+ protected void bindEditorsForExistingContact(String displayName, boolean isUserProfile,
+ ImmutableList<RawContact> rawContacts) {
+ }
+
+ @Override
+ protected void bindEditorsForNewContact(AccountWithDataSet account,
+ AccountType accountType) {
+ }
+
+ @Override
+ protected void bindEditors() {
+ }
+
+ @Override
+ protected void bindGroupMetaData() {
+ }
+
+ @Override
+ protected void bindMenuItemsForPhone(Contact contact) {
+ }
+
+ //
+ // ContactEditor
+ //
+
@Override
public void load(String action, Uri lookupUri, Bundle intentExtras) {
mAction = action;
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index 240a42f..70d5be2 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -16,28 +16,81 @@
package com.android.contacts.editor;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import com.android.contacts.GroupMetaDataLoader;
+import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
+import com.android.contacts.activities.ContactEditorBaseActivity;
+import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.Contact;
+import com.android.contacts.common.model.ContactLoader;
+import com.android.contacts.common.model.RawContact;
+import com.android.contacts.common.model.RawContactDeltaList;
+import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.quickcontact.QuickContactActivity;
+import android.accounts.Account;
+import android.app.Activity;
import android.app.Fragment;
+import android.app.LoaderManager;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.CursorLoader;
import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
+import android.os.SystemClock;
+import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.QuickContact;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+import android.widget.LinearLayout;
import java.util.ArrayList;
+import java.util.List;
/**
* Base Fragment for contact editors.
*/
-abstract public class ContactEditorBaseFragment extends Fragment {
+abstract public class ContactEditorBaseFragment extends Fragment implements
+ ContactEditor {
- protected static final String TAG = "ContactCompactEditor";
+ protected static final String TAG = "ContactEditor";
+
+ protected static final int LOADER_DATA = 1;
+ protected static final int LOADER_GROUPS = 2;
+
+ private static final String KEY_ACTION = "action";
+ private static final String KEY_URI = "uri";
+ private static final String KEY_AUTO_ADD_TO_DEFAULT_GROUP = "autoAddToDefaultGroup";
+ private static final String KEY_DISABLE_DELETE_MENU_OPTION = "disableDeleteMenuOption";
+ private static final String KEY_NEW_LOCAL_PROFILE = "newLocalProfile";
+
+ private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
+
+ private static final String KEY_RAW_CONTACTS = "rawContacts";
+
+ private static final String KEY_EDIT_STATE = "state";
+ private static final String KEY_STATUS = "status";
+
+ private static final String KEY_HAS_NEW_CONTACT = "hasNewContact";
+ private static final String KEY_NEW_CONTACT_READY = "newContactDataReady";
+
+ private static final String KEY_IS_EDIT = "isEdit";
+ private static final String KEY_EXISTING_CONTACT_READY = "existingContactDataReady";
+
+ protected static final int REQUEST_CODE_JOIN = 0;
+ protected static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1;
+ protected static final int REQUEST_CODE_PICK_RINGTONE = 2;
/**
* Callbacks for Activities that host contact editors Fragments.
@@ -98,6 +151,384 @@
void onDeleteRequested(Uri contactUri);
}
+ protected Context mContext;
+ protected Listener mListener;
+
+ protected LinearLayout mContent;
+
+ //
+ // Parameters passed in on {@link #load}
+ //
+ protected String mAction;
+ protected Uri mLookupUri;
+ protected Bundle mIntentExtras;
+ protected boolean mAutoAddToDefaultGroup;
+ protected boolean mDisableDeleteMenuOption;
+ protected boolean mNewLocalProfile;
+
+ //
+ // Helpers
+ //
+ protected ContactEditorUtils mEditorUtils;
+ protected RawContactDeltaComparator mComparator;
+ protected ViewIdGenerator mViewIdGenerator;
+
+ //
+ // Loaded data
+ //
+ // Used to temporarily store existing contact data during a rebind call (i.e. account switch)
+ protected ImmutableList<RawContact> mRawContacts;
+ protected Cursor mGroupMetaData;
+
+ //
+ // Contact editor state
+ //
+ protected RawContactDeltaList mState;
+ protected int mStatus;
+
+ // Whether to show the new contact blank form and if it's corresponding delta is ready.
+ protected boolean mHasNewContact;
+ protected boolean mNewContactDataReady;
+
+ // Whether it's an edit of existing contact and if it's corresponding delta is ready.
+ protected boolean mIsEdit;
+ protected boolean mExistingContactDataReady;
+
+ /**
+ * The contact data loader listener.
+ */
+ protected final LoaderManager.LoaderCallbacks<Contact> mDataLoaderListener =
+ new LoaderManager.LoaderCallbacks<Contact>() {
+
+ protected long mLoaderStartTime;
+
+ @Override
+ public Loader<Contact> onCreateLoader(int id, Bundle args) {
+ mLoaderStartTime = SystemClock.elapsedRealtime();
+ return new ContactLoader(mContext, mLookupUri, true);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Contact> loader, Contact data) {
+ final long loaderCurrentTime = SystemClock.elapsedRealtime();
+ Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime-mLoaderStartTime));
+ if (!data.isLoaded()) {
+ // Item has been deleted. Close activity without saving again.
+ Log.i(TAG, "No contact found. Closing activity");
+ mStatus = Status.CLOSING;
+ if (mListener != null) mListener.onContactNotFound();
+ return;
+ }
+
+ mStatus = Status.EDITING;
+ mLookupUri = data.getLookupUri();
+ final long setDataStartTime = SystemClock.elapsedRealtime();
+ setData(data);
+ final long setDataEndTime = SystemClock.elapsedRealtime();
+
+ Log.v(TAG, "Time needed for setting UI: " + (setDataEndTime - setDataStartTime));
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Contact> loader) {
+ }
+ };
+
+ /**
+ * The group meta data loader listener.
+ */
+ protected final LoaderManager.LoaderCallbacks<Cursor> mGroupLoaderListener =
+ new LoaderManager.LoaderCallbacks<Cursor>() {
+
+ @Override
+ public CursorLoader onCreateLoader(int id, Bundle args) {
+ return new GroupMetaDataLoader(mContext, ContactsContract.Groups.CONTENT_URI);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ mGroupMetaData = data;
+ bindGroupMetaData();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ }
+ };
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mContext = activity;
+ mEditorUtils = ContactEditorUtils.getInstance(mContext);
+ mComparator = new RawContactDeltaComparator(mContext);
+ }
+
+ @Override
+ public void onCreate(Bundle savedState) {
+ if (savedState != null) {
+ // Restore mUri before calling super.onCreate so that onInitializeLoaders
+ // would already have a uri and an action to work with
+ mAction = savedState.getString(KEY_ACTION);
+ mLookupUri = savedState.getParcelable(KEY_URI);
+ }
+
+ super.onCreate(savedState);
+
+ if (savedState == null) {
+ mViewIdGenerator = new ViewIdGenerator();
+ } else {
+ mViewIdGenerator = savedState.getParcelable(KEY_VIEW_ID_GENERATOR);
+
+ mAutoAddToDefaultGroup = savedState.getBoolean(KEY_AUTO_ADD_TO_DEFAULT_GROUP);
+ mDisableDeleteMenuOption = savedState.getBoolean(KEY_DISABLE_DELETE_MENU_OPTION);
+ mNewLocalProfile = savedState.getBoolean(KEY_NEW_LOCAL_PROFILE);
+
+ mRawContacts = ImmutableList.copyOf(savedState.<RawContact>getParcelableArrayList(
+ KEY_RAW_CONTACTS));
+ // NOTE: mGroupMetaData is not saved/restored
+
+ // Read state from savedState. No loading involved here
+ mState = savedState.<RawContactDeltaList> getParcelable(KEY_EDIT_STATE);
+ mStatus = savedState.getInt(KEY_STATUS);
+
+ mHasNewContact = savedState.getBoolean(KEY_HAS_NEW_CONTACT);
+ mNewContactDataReady = savedState.getBoolean(KEY_NEW_CONTACT_READY);
+
+ mIsEdit = savedState.getBoolean(KEY_IS_EDIT);
+ mExistingContactDataReady = savedState.getBoolean(KEY_EXISTING_CONTACT_READY);
+ }
+
+ // mState can still be null because it may not have have finished loading before
+ // onSaveInstanceState was called.
+ if (mState == null) {
+ mState = new RawContactDeltaList();
+ }
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ validateAction(mAction);
+
+ if (mState.isEmpty()) {
+ // The delta list may not have finished loading before orientation change happens.
+ // In this case, there will be a saved state but deltas will be missing. Reload from
+ // database.
+ if (Intent.ACTION_EDIT.equals(mAction)) {
+ // Either...
+ // 1) orientation change but load never finished.
+ // or
+ // 2) not an orientation change. data needs to be loaded for first time.
+ getLoaderManager().initLoader(LOADER_DATA, null, mDataLoaderListener);
+ }
+ } else {
+ // Orientation change, we already have mState, it was loaded by onCreate
+ bindEditors();
+ }
+
+ // Handle initial actions only when existing state missing
+ if (savedInstanceState == null) {
+ if (Intent.ACTION_EDIT.equals(mAction)) {
+ mIsEdit = true;
+ } else if (Intent.ACTION_INSERT.equals(mAction)) {
+ mHasNewContact = true;
+ final Account account = mIntentExtras == null ? null :
+ (Account) mIntentExtras.getParcelable(Intents.Insert.EXTRA_ACCOUNT);
+ final String dataSet = mIntentExtras == null ? null :
+ mIntentExtras.getString(Intents.Insert.EXTRA_DATA_SET);
+
+ if (account != null) {
+ // Account specified in Intent
+ createContact(new AccountWithDataSet(account.name, account.type, dataSet));
+ } else {
+ // No Account specified. Let the user choose
+ // Load Accounts async so that we can present them
+ selectAccountAndCreateContact();
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the requested action is valid.
+ *
+ * @param action The action to test.
+ * @throws IllegalArgumentException when the action is invalid.
+ */
+ private static void validateAction(String action) {
+ if (Intent.ACTION_EDIT.equals(action) || Intent.ACTION_INSERT.equals(action) ||
+ ContactEditorBaseActivity.ACTION_SAVE_COMPLETED.equals(action)) {
+ return;
+ }
+ throw new IllegalArgumentException("Unknown Action String " + action +
+ ". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT + " or " +
+ ContactEditorBaseActivity.ACTION_SAVE_COMPLETED);
+ }
+
+ @Override
+ public void onStart() {
+ getLoaderManager().initLoader(LOADER_GROUPS, null, mGroupLoaderListener);
+ super.onStart();
+ }
+
+ @Override
+ public void
+ onSaveInstanceState(Bundle outState) {
+ outState.putString(KEY_ACTION, mAction);
+ outState.putParcelable(KEY_URI, mLookupUri);
+ outState.putBoolean(KEY_AUTO_ADD_TO_DEFAULT_GROUP, mAutoAddToDefaultGroup);
+ outState.putBoolean(KEY_DISABLE_DELETE_MENU_OPTION, mDisableDeleteMenuOption);
+ outState.putBoolean(KEY_NEW_LOCAL_PROFILE, mNewLocalProfile);
+
+ outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
+
+ outState.putParcelableArrayList(KEY_RAW_CONTACTS, mRawContacts == null ?
+ Lists.<RawContact>newArrayList() : Lists.newArrayList(mRawContacts));
+ // NOTE: mGroupMetaData is not saved
+
+ if (hasValidState()) {
+ // Store entities with modifications
+ outState.putParcelable(KEY_EDIT_STATE, mState);
+ }
+ outState.putInt(KEY_STATUS, mStatus);
+ outState.putBoolean(KEY_HAS_NEW_CONTACT, mHasNewContact);
+ outState.putBoolean(KEY_NEW_CONTACT_READY, mNewContactDataReady);
+ outState.putBoolean(KEY_IS_EDIT, mIsEdit);
+ outState.putBoolean(KEY_EXISTING_CONTACT_READY, mExistingContactDataReady);
+
+ super.onSaveInstanceState(outState);
+ }
+
+ /**
+ * Check if our internal {@link #mState} is valid, usually checked before
+ * performing user actions.
+ */
+ protected boolean hasValidState() {
+ return mState.size() > 0;
+ }
+
+ private void setData(Contact contact) {
+
+ // If we have already loaded data, we do not want to change it here to not confuse the user
+ if (!mState.isEmpty()) {
+ Log.v(TAG, "Ignoring background change. This will have to be rebased later");
+ return;
+ }
+
+ // See if this edit operation needs to be redirected to a custom editor
+ mRawContacts = contact.getRawContacts();
+ if (mRawContacts.size() == 1) {
+ RawContact rawContact = mRawContacts.get(0);
+ String type = rawContact.getAccountTypeString();
+ String dataSet = rawContact.getDataSet();
+ AccountType accountType = rawContact.getAccountType(mContext);
+ if (accountType.getEditContactActivityClassName() != null &&
+ !accountType.areContactsWritable()) {
+ if (mListener != null) {
+ String name = rawContact.getAccountName();
+ long rawContactId = rawContact.getId();
+ mListener.onCustomEditContactActivityRequested(
+ new AccountWithDataSet(name, type, dataSet),
+ ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
+ mIntentExtras, true);
+ }
+ return;
+ }
+ }
+
+ String displayName = null;
+ // Check for writable raw contacts. If there are none, then we need to create one so user
+ // can edit. For the user profile case, there is already an editable contact.
+ if (!contact.isUserProfile() && !contact.isWritableContact(mContext)) {
+ mHasNewContact = true;
+
+ // This is potentially an asynchronous call and will add deltas to list.
+ selectAccountAndCreateContact();
+ displayName = contact.getDisplayName();
+ }
+
+ // This also adds deltas to list
+ // If displayName is null at this point it is simply ignored later on by the editor.
+ bindEditorsForExistingContact(displayName, contact.isUserProfile(),
+ mRawContacts);
+
+ bindMenuItemsForPhone(contact);
+ }
+
+ //
+ // Account creation
+ //
+
+ private void selectAccountAndCreateContact() {
+ // If this is a local profile, then skip the logic about showing the accounts changed
+ // activity and create a phone-local contact.
+ if (mNewLocalProfile) {
+ createContact(null);
+ return;
+ }
+
+ // If there is no default account or the accounts have changed such that we need to
+ // prompt the user again, then launch the account prompt.
+ if (mEditorUtils.shouldShowAccountChangedNotification()) {
+ Intent intent = new Intent(mContext, ContactEditorAccountsChangedActivity.class);
+ mStatus = Status.SUB_ACTIVITY;
+ startActivityForResult(intent, REQUEST_CODE_ACCOUNTS_CHANGED);
+ } else {
+ // Otherwise, there should be a default account. Then either create a local contact
+ // (if default account is null) or create a contact with the specified account.
+ AccountWithDataSet defaultAccount = mEditorUtils.getDefaultAccount();
+ createContact(defaultAccount);
+ }
+ }
+
+ /**
+ * Create a contact by automatically selecting the first account. If there's no available
+ * account, a device-local contact should be created.
+ */
+ protected void createContact() {
+ final List<AccountWithDataSet> accounts =
+ AccountTypeManager.getInstance(mContext).getAccounts(true);
+ // No Accounts available. Create a phone-local contact.
+ if (accounts.isEmpty()) {
+ createContact(null);
+ return;
+ }
+
+ // We have an account switcher in "create-account" screen, so don't need to ask a user to
+ // select an account here.
+ createContact(accounts.get(0));
+ }
+
+ /**
+ * Shows account creation screen associated with a given account.
+ *
+ * @param account may be null to signal a device-local contact should be created.
+ */
+ protected void createContact(AccountWithDataSet account) {
+ final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
+ final AccountType accountType = accountTypes.getAccountTypeForAccount(account);
+
+ if (accountType.getCreateContactActivityClassName() != null) {
+ if (mListener != null) {
+ mListener.onCustomCreateContactActivityRequested(account, mIntentExtras);
+ }
+ } else {
+ bindEditorsForNewContact(account, accountType);
+ }
+ }
+
+ // TODO: add javadocs after these are finalized
+ abstract protected void bindEditorsForExistingContact(String displayName, boolean isUserProfile,
+ ImmutableList<RawContact> rawContacts);
+ abstract protected void bindEditorsForNewContact(AccountWithDataSet account,
+ final AccountType accountType);
+ abstract protected void bindEditors();
+ abstract protected void bindGroupMetaData();
+ // TODO: should be removed after options menu is moved to base
+ abstract protected void bindMenuItemsForPhone(Contact contact);
+
/**
* Returns a legacy version of the given contactLookupUri if a legacy Uri was originally
* passed to the contact editor.
@@ -106,6 +537,7 @@
* @param requestLookupUri The lookup Uri originally passed to the contact editor
* (via Intent data), may be null.
*/
+ // TODO: move to ContactEditorUtils?
protected static Uri maybeConvertToLegacyLookupUri(Context context, Uri contactLookupUri,
Uri requestLookupUri) {
final String legacyAuthority = "contacts";
@@ -126,6 +558,7 @@
* Creates the result Intent for the given contactLookupUri that should started after a
* successful saving a contact.
*/
+ // TODO: move to ContactEditorUtils?
protected static Intent composeQuickContactsIntent(Context context, Uri contactLookupUri) {
final Intent intent = QuickContact.composeQuickContactsIntent(
context, (Rect) null, contactLookupUri, QuickContactActivity.MODE_FULLY_EXPANDED,
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index d9db85c..6639e1b 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -16,33 +16,25 @@
package com.android.contacts.editor;
-import android.accounts.Account;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
-import android.app.LoaderManager;
-import android.app.LoaderManager.LoaderCallbacks;
import android.content.ActivityNotFoundException;
import android.content.ContentUris;
import android.content.Context;
-import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.Loader;
-import android.database.Cursor;
import android.graphics.Bitmap;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
-import android.os.SystemClock;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
@@ -61,14 +53,11 @@
import android.widget.Toast;
import com.android.contacts.ContactSaveService;
-import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
-import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
import com.android.contacts.activities.ContactEditorActivity;
import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor;
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.model.Contact;
-import com.android.contacts.common.model.ContactLoader;
import com.android.contacts.common.model.RawContact;
import com.android.contacts.common.model.RawContactDelta;
import com.android.contacts.common.model.RawContactDeltaList;
@@ -103,27 +92,6 @@
AggregationSuggestionEngine.Listener, AggregationSuggestionView.Listener,
RawContactReadOnlyEditorView.Listener {
- private static final int LOADER_DATA = 1;
- private static final int LOADER_GROUPS = 2;
-
- private static final String KEY_ACTION = "action";
- private static final String KEY_URI = "uri";
- private static final String KEY_DISABLE_DELETE_MENU_OPTION = "disableDeleteMenuOption";
- private static final String KEY_NEW_LOCAL_PROFILE = "newLocalProfile";
-
- private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
-
- private static final String KEY_RAW_CONTACTS = "rawContacts";
-
- private static final String KEY_EDIT_STATE = "state";
- private static final String KEY_STATUS = "status";
-
- private static final String KEY_HAS_NEW_CONTACT = "hasNewContact";
- private static final String KEY_NEW_CONTACT_READY = "newContactDataReady";
-
- private static final String KEY_IS_EDIT = "isEdit";
- private static final String KEY_EXISTING_CONTACT_READY = "existingContactDataReady";
-
// Phone option menus
private static final String KEY_SEND_TO_VOICE_MAIL_STATE = "sendToVoicemailState";
private static final String KEY_ARE_PHONE_OPTIONS_CHANGEABLE = "arePhoneOptionsChangable";
@@ -161,54 +129,14 @@
public static final String INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION =
"disableDeleteMenuOption";
- private static final int REQUEST_CODE_JOIN = 0;
- private static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1;
- private static final int REQUEST_CODE_PICK_RINGTONE = 2;
-
- private Context mContext;
- private Listener mListener;
-
- private LinearLayout mContent;
-
- //
- // Parameters passed in on {@link #load}
- //
- private String mAction;
- private Uri mLookupUri;
- private Bundle mIntentExtras;
- private boolean mAutoAddToDefaultGroup;
- private boolean mDisableDeleteMenuOption = false;
- private boolean mNewLocalProfile = false;
-
//
// Helpers
//
- private ContactEditorUtils mEditorUtils;
- private RawContactDeltaComparator mComparator;
- private ViewIdGenerator mViewIdGenerator;
private AggregationSuggestionEngine mAggregationSuggestionEngine;
//
- // Loaded data
- //
- // Used to temporarily store existing contact data during a rebind call (i.e. account switch)
- private ImmutableList<RawContact> mRawContacts;
- private Cursor mGroupMetaData;
-
- //
// Contact editor state
//
- private RawContactDeltaList mState;
- private int mStatus;
-
- // Whether to show the new contact blank form and if it's corresponding delta is ready.
- private boolean mHasNewContact = false;
- private boolean mNewContactDataReady = false;
-
- // Whether it's an edit of existing contact and if it's corresponding delta is ready.
- private boolean mIsEdit = false;
- private boolean mExistingContactDataReady = false;
-
// Phone specific option menus
private boolean mSendToVoicemailState;
private boolean mArePhoneOptionsChangable;
@@ -330,14 +258,6 @@
}
@Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mContext = activity;
- mEditorUtils = ContactEditorUtils.getInstance(mContext);
- mComparator = new RawContactDeltaComparator(mContext);
- }
-
- @Override
public void onStop() {
super.onStop();
@@ -369,67 +289,6 @@
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- validateAction(mAction);
-
- if (mState.isEmpty()) {
- // The delta list may not have finished loading before orientation change happens.
- // In this case, there will be a saved state but deltas will be missing. Reload from
- // database.
- if (Intent.ACTION_EDIT.equals(mAction)) {
- // Either...
- // 1) orientation change but load never finished.
- // or
- // 2) not an orientation change. data needs to be loaded for first time.
- getLoaderManager().initLoader(LOADER_DATA, null, mDataLoaderListener);
- }
- } else {
- // Orientation change, we already have mState, it was loaded by onCreate
- bindEditors();
- }
-
- // Handle initial actions only when existing state missing
- if (savedInstanceState == null) {
- if (Intent.ACTION_EDIT.equals(mAction)) {
- mIsEdit = true;
- } else if (Intent.ACTION_INSERT.equals(mAction)) {
- mHasNewContact = true;
- final Account account = mIntentExtras == null ? null :
- (Account) mIntentExtras.getParcelable(Intents.Insert.EXTRA_ACCOUNT);
- final String dataSet = mIntentExtras == null ? null :
- mIntentExtras.getString(Intents.Insert.EXTRA_DATA_SET);
-
- if (account != null) {
- // Account specified in Intent
- createContact(new AccountWithDataSet(account.name, account.type, dataSet));
- } else {
- // No Account specified. Let the user choose
- // Load Accounts async so that we can present them
- selectAccountAndCreateContact();
- }
- }
- }
- }
-
- /**
- * Checks if the requested action is valid.
- *
- * @param action The action to test.
- * @throws IllegalArgumentException when the action is invalid.
- */
- private void validateAction(String action) {
- if (Intent.ACTION_EDIT.equals(action) || Intent.ACTION_INSERT.equals(action) ||
- ContactEditorActivity.ACTION_SAVE_COMPLETED.equals(action)) {
- return;
- }
- throw new IllegalArgumentException("Unknown Action String " + mAction +
- ". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT + " or " +
- ContactEditorActivity.ACTION_SAVE_COMPLETED);
- }
-
- @Override
public void onStart() {
getLoaderManager().initLoader(LOADER_GROUPS, null, mGroupLoaderListener);
super.onStart();
@@ -455,42 +314,13 @@
@Override
public void onCreate(Bundle savedState) {
- if (savedState != null) {
- // Restore mUri before calling super.onCreate so that onInitializeLoaders
- // would already have a uri and an action to work with
- mAction = savedState.getString(KEY_ACTION);
- mLookupUri = savedState.getParcelable(KEY_URI);
- }
-
super.onCreate(savedState);
- if (savedState == null) {
- // If savedState is non-null, onRestoreInstanceState() will restore the generator.
- mViewIdGenerator = new ViewIdGenerator();
- } else {
- mViewIdGenerator = savedState.getParcelable(KEY_VIEW_ID_GENERATOR);
-
- mDisableDeleteMenuOption = savedState.getBoolean(KEY_DISABLE_DELETE_MENU_OPTION);
- mNewLocalProfile = savedState.getBoolean(KEY_NEW_LOCAL_PROFILE);
-
- mRawContacts = ImmutableList.copyOf(savedState.<RawContact>getParcelableArrayList(
- KEY_RAW_CONTACTS));
- // NOTE: mGroupMetaData is not saved/restored
-
- // Read state from savedState. No loading involved here
- mState = savedState.<RawContactDeltaList> getParcelable(KEY_EDIT_STATE);
- mStatus = savedState.getInt(KEY_STATUS);
-
- mHasNewContact = savedState.getBoolean(KEY_HAS_NEW_CONTACT);
- mNewContactDataReady = savedState.getBoolean(KEY_NEW_CONTACT_READY);
-
- mIsEdit = savedState.getBoolean(KEY_IS_EDIT);
- mExistingContactDataReady = savedState.getBoolean(KEY_EXISTING_CONTACT_READY);
-
+ if (savedState != null) {
// Phone specific options menus
mSendToVoicemailState = savedState.getBoolean(KEY_SEND_TO_VOICE_MAIL_STATE);
mArePhoneOptionsChangable = savedState.getBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE);
- mCustomRingtone = savedState.getString(KEY_CUSTOM_RINGTONE);
+ mCustomRingtone = savedState.getString(KEY_CUSTOM_RINGTONE);
// Joins
mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
@@ -515,60 +345,6 @@
mAggregationSuggestionsRawContactId = savedState.getLong(
KEY_AGGREGATION_SUGGESTIONS_RAW_CONTACT_ID);
}
-
- // mState can still be null because it may not have have finished loading before
- // onSaveInstanceState was called.
- if (mState == null) {
- mState = new RawContactDeltaList();
- }
- }
-
- public void setData(Contact contact) {
-
- // If we have already loaded data, we do not want to change it here to not confuse the user
- if (!mState.isEmpty()) {
- Log.v(TAG, "Ignoring background change. This will have to be rebased later");
- return;
- }
-
- // See if this edit operation needs to be redirected to a custom editor
- mRawContacts = contact.getRawContacts();
- if (mRawContacts.size() == 1) {
- RawContact rawContact = mRawContacts.get(0);
- String type = rawContact.getAccountTypeString();
- String dataSet = rawContact.getDataSet();
- AccountType accountType = rawContact.getAccountType(mContext);
- if (accountType.getEditContactActivityClassName() != null &&
- !accountType.areContactsWritable()) {
- if (mListener != null) {
- String name = rawContact.getAccountName();
- long rawContactId = rawContact.getId();
- mListener.onCustomEditContactActivityRequested(
- new AccountWithDataSet(name, type, dataSet),
- ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
- mIntentExtras, true);
- }
- return;
- }
- }
-
- String displayName = null;
- // Check for writable raw contacts. If there are none, then we need to create one so user
- // can edit. For the user profile case, there is already an editable contact.
- if (!contact.isUserProfile() && !contact.isWritableContact(mContext)) {
- mHasNewContact = true;
-
- // This is potentially an asynchronous call and will add deltas to list.
- selectAccountAndCreateContact();
- displayName = contact.getDisplayName();
- }
-
- // This also adds deltas to list
- // If displayName is null at this point it is simply ignored later on by the editor.
- bindEditorsForExistingContact(displayName, contact.isUserProfile(),
- mRawContacts);
-
- bindMenuItemsForPhone(contact);
}
@Override
@@ -581,7 +357,8 @@
updatedExpandedEditorsMap();
}
- private void bindEditorsForExistingContact(String displayName, boolean isUserProfile,
+ @Override
+ protected void bindEditorsForExistingContact(String displayName, boolean isUserProfile,
ImmutableList<RawContact> rawContacts) {
setEnabled(true);
mDefaultDisplayName = displayName;
@@ -619,7 +396,8 @@
bindEditors();
}
- private void bindMenuItemsForPhone(Contact contact) {
+ @Override
+ protected void bindMenuItemsForPhone(Contact contact) {
mSendToVoicemailState = contact.isSendToVoicemail();
mCustomRingtone = contact.getCustomRingtone();
mArePhoneOptionsChangable = arePhoneOptionsChangable(contact);
@@ -649,64 +427,6 @@
}
}
- private void selectAccountAndCreateContact() {
- // If this is a local profile, then skip the logic about showing the accounts changed
- // activity and create a phone-local contact.
- if (mNewLocalProfile) {
- createContact(null);
- return;
- }
-
- // If there is no default account or the accounts have changed such that we need to
- // prompt the user again, then launch the account prompt.
- if (mEditorUtils.shouldShowAccountChangedNotification()) {
- Intent intent = new Intent(mContext, ContactEditorAccountsChangedActivity.class);
- mStatus = Status.SUB_ACTIVITY;
- startActivityForResult(intent, REQUEST_CODE_ACCOUNTS_CHANGED);
- } else {
- // Otherwise, there should be a default account. Then either create a local contact
- // (if default account is null) or create a contact with the specified account.
- AccountWithDataSet defaultAccount = mEditorUtils.getDefaultAccount();
- createContact(defaultAccount);
- }
- }
-
- /**
- * Create a contact by automatically selecting the first account. If there's no available
- * account, a device-local contact should be created.
- */
- private void createContact() {
- final List<AccountWithDataSet> accounts =
- AccountTypeManager.getInstance(mContext).getAccounts(true);
- // No Accounts available. Create a phone-local contact.
- if (accounts.isEmpty()) {
- createContact(null);
- return;
- }
-
- // We have an account switcher in "create-account" screen, so don't need to ask a user to
- // select an account here.
- createContact(accounts.get(0));
- }
-
- /**
- * Shows account creation screen associated with a given account.
- *
- * @param account may be null to signal a device-local contact should be created.
- */
- private void createContact(AccountWithDataSet account) {
- final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
- final AccountType accountType = accountTypes.getAccountTypeForAccount(account);
-
- if (accountType.getCreateContactActivityClassName() != null) {
- if (mListener != null) {
- mListener.onCustomCreateContactActivityRequested(account, mIntentExtras);
- }
- } else {
- bindEditorsForNewContact(account, accountType);
- }
- }
-
/**
* Removes a current editor ({@link #mState}) and rebinds new editor for a new account.
* Some of old data are reused with new restriction enforced by the new account.
@@ -738,7 +458,8 @@
}
}
- private void bindEditorsForNewContact(AccountWithDataSet account,
+ @Override
+ protected void bindEditorsForNewContact(AccountWithDataSet account,
final AccountType accountType) {
bindEditorsForNewContact(account, accountType, null, null);
}
@@ -783,7 +504,8 @@
bindEditors();
}
- private void bindEditors() {
+ @Override
+ protected void bindEditors() {
// bindEditors() can only bind views if there is data in mState, so immediately return
// if mState is null
if (mState.isEmpty()) {
@@ -989,7 +711,8 @@
}
}
- private void bindGroupMetaData() {
+ @Override
+ protected void bindGroupMetaData() {
if (mGroupMetaData == null) {
return;
}
@@ -1178,14 +901,6 @@
}
/**
- * Check if our internal {@link #mState} is valid, usually checked before
- * performing user actions.
- */
- private boolean hasValidState() {
- return mState.size() > 0;
- }
-
- /**
* Return true if there are any edits to the current contact which need to
* be saved.
*/
@@ -1318,14 +1033,12 @@
if (mListener != null) mListener.onReverted();
}
- public void doSaveAction() {
- save(SaveMode.CLOSE);
- }
-
+ @Override
public void onJoinCompleted(Uri uri) {
onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri);
}
+ @Override
public void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded,
Uri contactLookupUri) {
if (hadChanges) {
@@ -1619,28 +1332,6 @@
@Override
public void onSaveInstanceState(Bundle outState) {
- outState.putString(KEY_ACTION, mAction);
- outState.putParcelable(KEY_URI, mLookupUri);
- outState.putBoolean(KEY_DISABLE_DELETE_MENU_OPTION, mDisableDeleteMenuOption);
- outState.putBoolean(KEY_NEW_LOCAL_PROFILE, mNewLocalProfile);
-
- outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
-
- outState.putParcelableArrayList(KEY_RAW_CONTACTS, mRawContacts == null
- ? Lists.<RawContact>newArrayList() : Lists.newArrayList(mRawContacts));
-
- if (hasValidState()) {
- // Store entities with modifications
- outState.putParcelable(KEY_EDIT_STATE, mState);
- }
- outState.putInt(KEY_STATUS, mStatus);
-
- outState.putBoolean(KEY_HAS_NEW_CONTACT, mHasNewContact);
- outState.putBoolean(KEY_NEW_CONTACT_READY, mNewContactDataReady);
-
- outState.putBoolean(KEY_IS_EDIT, mIsEdit);
- outState.putBoolean(KEY_EXISTING_CONTACT_READY, mExistingContactDataReady);
-
// Phone specific options
outState.putBoolean(KEY_SEND_TO_VOICE_MAIL_STATE, mSendToVoicemailState);
outState.putBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE, mArePhoneOptionsChangable);
@@ -1651,9 +1342,7 @@
outState.putBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN, mContactWritableForJoin);
outState.putSerializable(KEY_EXPANDED_EDITORS, mExpandedEditors);
-
outState.putBoolean(KEY_IS_USER_PROFILE, mIsUserProfile);
-
outState.putBoolean(KEY_ENABLED, mEnabled);
// Photos
@@ -1801,68 +1490,6 @@
return false;
}
- /**
- * The listener for the data loader
- */
- private final LoaderManager.LoaderCallbacks<Contact> mDataLoaderListener =
- new LoaderCallbacks<Contact>() {
-
- private long mLoaderStartTime;
-
- @Override
- public Loader<Contact> onCreateLoader(int id, Bundle args) {
- mLoaderStartTime = SystemClock.elapsedRealtime();
- return new ContactLoader(mContext, mLookupUri, true);
- }
-
- @Override
- public void onLoadFinished(Loader<Contact> loader, Contact data) {
- final long loaderCurrentTime = SystemClock.elapsedRealtime();
- Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime-mLoaderStartTime));
- if (!data.isLoaded()) {
- // Item has been deleted. Close activity without saving again.
- Log.i(TAG, "No contact found. Closing activity");
- mStatus = Status.CLOSING;
- if (mListener != null) mListener.onContactNotFound();
- return;
- }
-
- mStatus = Status.EDITING;
- mLookupUri = data.getLookupUri();
- final long setDataStartTime = SystemClock.elapsedRealtime();
- setData(data);
- final long setDataEndTime = SystemClock.elapsedRealtime();
-
- Log.v(TAG, "Time needed for setting UI: " + (setDataEndTime-setDataStartTime));
- }
-
- @Override
- public void onLoaderReset(Loader<Contact> loader) {
- }
- };
-
- /**
- * The listener for the group meta data loader for all groups.
- */
- private final LoaderManager.LoaderCallbacks<Cursor> mGroupLoaderListener =
- new LoaderCallbacks<Cursor>() {
-
- @Override
- public CursorLoader onCreateLoader(int id, Bundle args) {
- return new GroupMetaDataLoader(mContext, Groups.CONTENT_URI);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- mGroupMetaData = data;
- bindGroupMetaData();
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- }
- };
-
@Override
public void onSplitContactConfirmed() {
if (mState.isEmpty()) {