New editor flow (for master)

- Added dialog-themed activity that pops up on new contact creation
in 3 cases with a variation of message when there are:
  * 0 writable accounts on the device
  * 1 writable account
  * 2+ writable accounts

- The dialog is displayed whenever a new account is added
to the device or the default account has been removed

- Once an account selection has been made by the user,
we store it in SharedPreferences using ContactEditorUtils

- Slight restyling of the account list adapter

Bug: 5355671

Original CL: Ib3343a5aea972b366a9df41b9419ad9561c2243d

Change-Id: I46f4c5687a5b6eaa68b55a568f0d737ad80dfc5c
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index df65877..18782b9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -503,6 +503,13 @@
             android:windowSoftInputMode="adjustResize"
             android:exported="false"/>
 
+        <!-- Accounts changed prompt that can appear when creating a new contact. -->
+        <activity
+            android:name=".activities.ContactEditorAccountsChangedActivity"
+            android:theme="@style/ContactEditorAccountsChangedActivityTheme"
+            android:windowSoftInputMode="adjustResize"
+            android:exported="false"/>
+
         <!-- Create a new or edit an existing contact -->
         <activity
             android:name=".activities.ContactEditorActivity"
diff --git a/res/layout/account_selector_list_item.xml b/res/layout/account_selector_list_item.xml
index a700866..4cba3e9 100644
--- a/res/layout/account_selector_list_item.xml
+++ b/res/layout/account_selector_list_item.xml
@@ -44,6 +44,7 @@
             android:layout_height="wrap_content"
             android:layout_marginRight="8dip"
             android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"
             android:singleLine="true"
             android:ellipsize="end"/>
     </LinearLayout>
diff --git a/res/layout/contact_editor_accounts_changed_activity_with_picker.xml b/res/layout/contact_editor_accounts_changed_activity_with_picker.xml
new file mode 100644
index 0000000..a5aab20
--- /dev/null
+++ b/res/layout/contact_editor_accounts_changed_activity_with_picker.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!--
+  Layout for account prompt (which includes a ListView) that can appear when
+  the user creates a new contact.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="15dip"
+        android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:background="?android:attr/listDivider"/>
+
+    <ListView android:id="@+id/account_list"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1"
+        android:fadingEdge="none"/>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:background="?android:attr/listDivider"/>
+
+    <Button
+        android:id="@+id/add_account_button"
+        style="?android:attr/buttonBarButtonStyle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/res/layout/contact_editor_accounts_changed_activity_with_text.xml b/res/layout/contact_editor_accounts_changed_activity_with_text.xml
new file mode 100644
index 0000000..33714ea
--- /dev/null
+++ b/res/layout/contact_editor_accounts_changed_activity_with_text.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!--
+  Layout for account prompt (which just includes text and 2 buttons) that can appear when the user
+  creates a new contact.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView android:id="@+id/text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="15dip"
+        android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:background="?android:attr/listDivider"/>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        style="?android:attr/buttonBarStyle">
+
+        <Button
+            android:id="@+id/left_button"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+
+        <Button
+            android:id="@+id/right_button"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 6a64dfd..e3de72b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -208,6 +208,12 @@
         <item name="android:windowCloseOnTouchOutside">true</item>
     </style>
 
+    <style name="ContactEditorAccountsChangedActivityTheme" parent="@android:style/Theme.Holo.Light.Dialog.NoActionBar.MinWidth">
+        <item name="android:windowCloseOnTouchOutside">true</item>
+        <item name="android:textColorPrimary">@color/primary_text_color</item>
+        <item name="android:textColorSecondary">@color/secondary_text_color</item>
+    </style>
+
     <style name="SectionDivider">
         <item name="android:background">#7e7e87</item>
         <item name="android:layout_height">1dip</item>
diff --git a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
new file mode 100644
index 0000000..d63ea6a
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.contacts.activities;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.ContactsContract.Intents;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.contacts.R;
+import com.android.contacts.editor.ContactEditorUtils;
+import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.util.AccountsListAdapter;
+
+import java.util.List;
+
+/**
+ * This activity can be shown to the user when creating a new contact to inform the user about
+ * which account the contact will be saved in. There is also an option to add an account at
+ * this time. The {@link Intent} in the activity result will contain an extra
+ * {@link #Intents.Insert.ACCOUNT} that contains the {@link AccountWithDataSet} to create
+ * the new contact in. If the activity result doesn't contain intent data, then there is no
+ * account for this contact.
+ */
+public class ContactEditorAccountsChangedActivity extends Activity {
+
+    private static final String TAG = ContactEditorAccountsChangedActivity.class.getSimpleName();
+
+    private static final int SUBACTIVITY_ADD_NEW_ACCOUNT = 1;
+
+    private AccountsListAdapter mAccountListAdapter;
+    private ContactEditorUtils mEditorUtils;
+
+    private final OnItemClickListener mAccountListItemClickListener = new OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            if (mAccountListAdapter == null) {
+                return;
+            }
+            saveAccountAndReturnResult(mAccountListAdapter.getItem(position));
+        }
+    };
+
+    private final OnClickListener mAddAccountClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            startActivityForResult(mEditorUtils.createAddWritableAccountIntent(),
+                    SUBACTIVITY_ADD_NEW_ACCOUNT);
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mEditorUtils = ContactEditorUtils.getInstance(this);
+        final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(this).
+                getAccounts(true);
+        final int numAccounts = accounts.size();
+        if (numAccounts < 0) {
+            throw new IllegalStateException("Cannot have a negative number of accounts");
+        }
+
+        if (numAccounts >= 2) {
+            // When the user has 2+ writable accounts, show a list of accounts so the user can pick
+            // which account to create a contact in.
+            setContentView(R.layout.contact_editor_accounts_changed_activity_with_picker);
+
+            final TextView textView = (TextView) findViewById(R.id.text);
+            textView.setText(getString(R.string.contact_editor_prompt_multiple_accounts));
+
+            final Button button = (Button) findViewById(R.id.add_account_button);
+            button.setText(getString(R.string.add_new_account));
+            button.setOnClickListener(mAddAccountClickListener);
+
+            final ListView accountListView = (ListView) findViewById(R.id.account_list);
+            mAccountListAdapter = new AccountsListAdapter(this, true);
+            accountListView.setAdapter(mAccountListAdapter);
+            accountListView.setOnItemClickListener(mAccountListItemClickListener);
+        } else if (numAccounts == 1) {
+            // If the user has 1 writable account we will just show the user a message with 2
+            // possible action buttons.
+            setContentView(R.layout.contact_editor_accounts_changed_activity_with_text);
+
+            final TextView textView = (TextView) findViewById(R.id.text);
+            final Button leftButton = (Button) findViewById(R.id.left_button);
+            final Button rightButton = (Button) findViewById(R.id.right_button);
+
+            final AccountWithDataSet account = accounts.get(0);
+            textView.setText(getString(R.string.contact_editor_prompt_one_account,
+                    account.name));
+
+            // This button allows the user to add a new account to the device and return to
+            // this app afterwards.
+            leftButton.setText(getString(R.string.add_new_account));
+            leftButton.setOnClickListener(mAddAccountClickListener);
+
+            // This button allows the user to continue creating the contact in the specified
+            // account.
+            rightButton.setText(getString(android.R.string.ok));
+            rightButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    saveAccountAndReturnResult(account);
+                }
+            });
+        } else {
+            // If the user has 0 writable accounts, we will just show the user a message with 2
+            // possible action buttons.
+            setContentView(R.layout.contact_editor_accounts_changed_activity_with_text);
+
+            final TextView textView = (TextView) findViewById(R.id.text);
+            final Button leftButton = (Button) findViewById(R.id.left_button);
+            final Button rightButton = (Button) findViewById(R.id.right_button);
+
+            textView.setText(getString(R.string.contact_editor_prompt_zero_accounts));
+
+            // This button allows the user to continue editing the contact as a phone-only
+            // local contact.
+            leftButton.setText(getString(R.string.keep_local));
+            leftButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    // Remember that the user wants to create local contacts, so the user is not
+                    // prompted again with this activity.
+                    mEditorUtils.saveDefaultAndAllAccounts(null);
+                    setResult(RESULT_OK);
+                    finish();
+                }
+            });
+
+            // This button allows the user to add a new account to the device and return to
+            // this app afterwards.
+            rightButton.setText(getString(R.string.add_account));
+            rightButton.setOnClickListener(mAddAccountClickListener);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == SUBACTIVITY_ADD_NEW_ACCOUNT) {
+            // If the user canceled the account setup process, then keep this activity visible to
+            // the user.
+            if (resultCode != RESULT_OK) {
+                return;
+            }
+            // Subactivity was successful, so pass the result back and finish the activity.
+            AccountWithDataSet account = mEditorUtils.getCreatedAccount(resultCode, data);
+            if (account == null) {
+                setResult(resultCode);
+                finish();
+                return;
+            }
+            saveAccountAndReturnResult(account);
+        }
+    }
+
+    private void saveAccountAndReturnResult(AccountWithDataSet account) {
+        // Save this as the default account
+        mEditorUtils.saveDefaultAndAllAccounts(account);
+
+        // Pass account info in activity result intent
+        Intent intent = new Intent();
+        intent.putExtra(Intents.Insert.ACCOUNT, account);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index a8c0b36..062c021 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -20,6 +20,7 @@
 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.JoinContactActivity;
 import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
@@ -192,6 +193,7 @@
     private static final int REQUEST_CODE_JOIN = 0;
     private static final int REQUEST_CODE_CAMERA_WITH_DATA = 1;
     private static final int REQUEST_CODE_PHOTO_PICKED_WITH_DATA = 2;
+    private static final int REQUEST_CODE_ACCOUNTS_CHANGED = 3;
 
     private Bitmap mPhoto = null;
     private long mRawContactIdRequestingPhoto = -1;
@@ -218,6 +220,8 @@
     private long mContactIdForJoin;
     private boolean mContactWritableForJoin;
 
+    private ContactEditorUtils mEditorUtils;
+
     private LinearLayout mContent;
     private EntityDeltaList mState;
 
@@ -316,6 +320,7 @@
     public void onAttach(Activity activity) {
         super.onAttach(activity);
         mContext = activity;
+        mEditorUtils = ContactEditorUtils.getInstance(mContext);
         loadPhotoPickSize();
     }
 
@@ -370,7 +375,7 @@
                 } else {
                     // No Account specified. Let the user choose
                     // Load Accounts async so that we can present them
-                    createContact();
+                    selectAccountAndCreateContact();
                 }
             } else if (ContactEditorActivity.ACTION_SAVE_COMPLETED.equals(mAction)) {
                 // do nothing
@@ -528,17 +533,43 @@
         }
     }
 
+    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();
+            if (defaultAccount == null) {
+                createContact(null);
+            } else {
+                createContact(defaultAccount);
+            }
+        }
+    }
+
     /**
-     * Shows the account creation screen. An account associated with the contact is automatically
-     * selected. If there's no available account, device-local contact should be created.
+     * 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 or creating a local profile.  Create a phone-local contact.
-        if (accounts.isEmpty() || mNewLocalProfile) {
+        // No Accounts available. Create a phone-local contact.
+        if (accounts.isEmpty()) {
             createContact(null);
-            return;  // Don't show a dialog.
+            return;
         }
 
         // We have an account switcher in "create-account" screen, so don't need to ask a user to
@@ -546,7 +577,6 @@
         createContact(accounts.get(0));
     }
 
-
     /**
      * Shows account creation screen associated with a given account.
      *
@@ -760,6 +790,28 @@
         }
     }
 
+    private void saveDefaultAccountIfNecessary() {
+        // Verify that this is a newly created contact, that the contact is composed of only
+        // 1 raw contact, and that the contact is not a user profile.
+        if (!Intent.ACTION_INSERT.equals(mAction) && mState.size() == 1 &&
+                !isEditingUserProfile()) {
+            return;
+        }
+
+        // Find the associated account for this contact (retrieve it here because there are
+        // multiple paths to creating a contact and this ensures we always have the correct
+        // account).
+        final EntityDelta entity = mState.get(0);
+        final ValuesDelta values = entity.getValues();
+        String name = values.getAsString(RawContacts.ACCOUNT_NAME);
+        String type = values.getAsString(RawContacts.ACCOUNT_TYPE);
+        String dataSet = values.getAsString(RawContacts.DATA_SET);
+
+        AccountWithDataSet account = (name == null || type == null) ? null :
+                new AccountWithDataSet(name, type, dataSet);
+        mEditorUtils.saveDefaultAndAllAccounts(account);
+    }
+
     private void addAccountSwitcher(
             final EntityDelta currentState, BaseRawContactEditorView editor) {
         ValuesDelta values = currentState.getValues();
@@ -985,6 +1037,10 @@
 
         setEnabled(false);
 
+        // Store account as default account, only if this is a new contact
+        saveDefaultAccountIfNecessary();
+
+        // Save contact
         Intent intent = ContactSaveService.createSaveContactIntent(getActivity(), mState,
                 SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
                 getActivity().getClass(), ContactEditorActivity.ACTION_SAVE_COMPLETED);
@@ -1536,10 +1592,10 @@
             mStatus = Status.EDITING;
         }
 
-        // Ignore failed requests
-        if (resultCode != Activity.RESULT_OK) return;
         switch (requestCode) {
             case REQUEST_CODE_PHOTO_PICKED_WITH_DATA: {
+                // Ignore failed requests
+                if (resultCode != Activity.RESULT_OK) return;
                 // As we are coming back to this view, the editor will be reloaded automatically,
                 // which will cause the photo that is set here to disappear. To prevent this,
                 // we remember to set a flag which is interpreted after loading.
@@ -1552,16 +1608,40 @@
                 break;
             }
             case REQUEST_CODE_CAMERA_WITH_DATA: {
+                // Ignore failed requests
+                if (resultCode != Activity.RESULT_OK) return;
                 doCropPhoto(mCurrentPhotoFile);
                 break;
             }
             case REQUEST_CODE_JOIN: {
+                // Ignore failed requests
+                if (resultCode != Activity.RESULT_OK) return;
                 if (data != null) {
                     final long contactId = ContentUris.parseId(data.getData());
                     joinAggregate(contactId);
                 }
                 break;
             }
+            case REQUEST_CODE_ACCOUNTS_CHANGED: {
+                // Bail if the account selector was not successful.
+                if (resultCode != Activity.RESULT_OK) {
+                    mListener.onReverted();
+                    return;
+                }
+                // If there's an account specified, use it.
+                if (data != null) {
+                    AccountWithDataSet account = data.getParcelableExtra(Intents.Insert.ACCOUNT);
+                    if (account != null) {
+                        createContact(account);
+                        return;
+                    }
+                }
+                // If there isn't an account specified, then this is likely a phone-local
+                // contact, so we should continue setting up the editor by automatically selecting
+                // the most appropriate account.
+                createContact();
+                break;
+            }
         }
     }
 
diff --git a/src/com/android/contacts/util/AccountsListAdapter.java b/src/com/android/contacts/util/AccountsListAdapter.java
index d065255..fc48e72 100644
--- a/src/com/android/contacts/util/AccountsListAdapter.java
+++ b/src/com/android/contacts/util/AccountsListAdapter.java
@@ -70,19 +70,20 @@
         final View resultView = convertView != null ? convertView
                 : mInflater.inflate(R.layout.account_selector_list_item, parent, false);
 
-        final TextView text1 = (TextView)resultView.findViewById(android.R.id.text1);
-        final TextView text2 = (TextView)resultView.findViewById(android.R.id.text2);
-        final ImageView icon = (ImageView)resultView.findViewById(android.R.id.icon);
+        final TextView text1 = (TextView) resultView.findViewById(android.R.id.text1);
+        final TextView text2 = (TextView) resultView.findViewById(android.R.id.text2);
+        final ImageView icon = (ImageView) resultView.findViewById(android.R.id.icon);
 
         final AccountWithDataSet account = mAccounts.get(position);
         final AccountType accountType = mAccountTypes.getAccountType(account.type, account.dataSet);
 
-        text1.setText(account.name);
+        text1.setText(accountType.getDisplayLabel(mContext));
 
         // For email addresses, we don't want to truncate at end, which might cut off the domain
         // name.
-        text1.setEllipsize(TruncateAt.MIDDLE);
-        text2.setText(accountType.getDisplayLabel(mContext));
+        text2.setText(account.name);
+        text2.setEllipsize(TruncateAt.MIDDLE);
+
         icon.setImageDrawable(accountType.getDisplayIcon(mContext));
 
         return resultView;