Merge "Fix name editor data loss on rotation"
diff --git a/res/layout/external_raw_contact_editor_view.xml b/res/layout/raw_contact_readonly_editor_view.xml
similarity index 94%
rename from res/layout/external_raw_contact_editor_view.xml
rename to res/layout/raw_contact_readonly_editor_view.xml
index f1ba198..b34028a 100644
--- a/res/layout/external_raw_contact_editor_view.xml
+++ b/res/layout/raw_contact_readonly_editor_view.xml
@@ -14,8 +14,7 @@
      limitations under the License.
 -->
 
-<!-- placed inside act_edit as tabcontent -->
-<com.android.contacts.editor.ExternalRawContactEditorView
+<com.android.contacts.editor.RawContactReadOnlyEditorView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
@@ -77,4 +76,4 @@
         android:layout_height="wrap_content"
         android:orientation="vertical"/>
 
-</com.android.contacts.editor.ExternalRawContactEditorView>
+</com.android.contacts.editor.RawContactReadOnlyEditorView>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 075cbea..ed9dd0f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1799,4 +1799,22 @@
 
     <!-- Message in the standard "no account" prompt that encourages the user to add a Google account before continuing to use the People app [CHAR LIMIT=NONE] -->
     <string name="no_account_prompt">People works better with a Google Account.\n\n\u2022 Access from any web browser.\n\u2022 Back up your contacts securely.</string>
+
+    <!-- Message in the contact editor prompt that notifies the user that the newly created contact will not be saved to any account, and prompts addition of an account [CHAR LIMIT=NONE] -->
+    <string name="contact_editor_prompt_zero_accounts">Your new contact won\'t be backed up. Add an account that backs up contacts online?</string>
+
+    <!-- Message in the contact editor prompt that asks the user if it's okay to save the newly created contact to the account shown. [CHAR LIMIT=NONE] -->
+    <string name="contact_editor_prompt_one_account">Your new contact will be synchronized with <xliff:g id="account_name">%1$s</xliff:g>.</string>
+
+    <!-- Message in the contact editor prompt that asks the user which account they want to save the newly created contact to. [CHAR LIMIT=NONE] -->
+    <string name="contact_editor_prompt_multiple_accounts">You can synchronize your new contact with one of the following accounts. Which do you want to use?</string>
+
+    <!-- Button label to indicate that the user wants to save the newly created contact locally (instead of backing it up online) [CHAR LIMIT=20] -->
+    <string name="keep_local">Keep local</string>
+
+    <!-- Button label to prompt the user to add an account (when there are 0 existing accounts on the device) [CHAR LIMIT=30] -->
+    <string name="add_account">Add account</string>
+
+    <!-- Button label to prompt the user to add another account (when there are already existing accounts on the device) [CHAR LIMIT=30] -->
+    <string name="add_new_account">Add new account</string>
 </resources>
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index 4007916..ff4ab8b 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -22,6 +22,7 @@
 import com.google.common.annotations.VisibleForTesting;
 
 import android.app.Application;
+import android.app.FragmentManager;
 import android.app.LoaderManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -109,10 +110,14 @@
         Context context = getApplicationContext();
         PreferenceManager.getDefaultSharedPreferences(context);
         AccountTypeManager.getInstance(context);
-        LoaderManager.enableDebugLogging(true);
 
-        StrictMode.setThreadPolicy(
-                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        LoaderManager.enableDebugLogging(Log.isLoggable(Constants.LOADER_MANAGER_TAG, Log.DEBUG));
+        FragmentManager.enableDebugLogging(
+                Log.isLoggable(Constants.FRAGMENT_MANAGER_TAG, Log.DEBUG));
+        if (Log.isLoggable(Constants.STRICT_MODE_TAG, Log.DEBUG)) {
+            StrictMode.setThreadPolicy(
+                    new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        }
 
         if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
             Log.d(Constants.PERFORMANCE_TAG, "ContactsApplication.onCreate finish");
diff --git a/src/com/android/contacts/GroupMemberLoader.java b/src/com/android/contacts/GroupMemberLoader.java
index 9605747..a75da48 100644
--- a/src/com/android/contacts/GroupMemberLoader.java
+++ b/src/com/android/contacts/GroupMemberLoader.java
@@ -16,6 +16,7 @@
 package com.android.contacts;
 
 import com.android.contacts.list.ContactListAdapter;
+import com.android.contacts.preference.ContactsPreferences;
 
 import android.content.Context;
 import android.content.CursorLoader;
@@ -81,7 +82,13 @@
         setProjection(PROJECTION_DATA);
         setSelection(createSelection());
         setSelectionArgs(createSelectionArgs());
-        setSortOrder(Contacts.SORT_KEY_ALTERNATIVE);
+
+        ContactsPreferences prefs = new ContactsPreferences(context);
+        if (prefs.getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+            setSortOrder(Contacts.SORT_KEY_PRIMARY);
+        } else {
+            setSortOrder(Contacts.SORT_KEY_ALTERNATIVE);
+        }
     }
 
     private Uri createUri() {
diff --git a/src/com/android/contacts/activities/ConfirmAddDetailActivity.java b/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
index 95a39e7..4b297d9 100644
--- a/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
+++ b/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
@@ -560,8 +560,7 @@
             final String dataSet = state.getValues().getAsString(RawContacts.DATA_SET);
             final AccountType type = mAccountTypeManager.getAccountType(accountType, dataSet);
 
-            // Raw contacts that are not from external sources should be editable.
-            if (!type.isExternal()) {
+            if (type.areContactsWritable()) {
                 mEditableAccountType = type;
                 mState = state;
                 return;
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index a7a1ba1..605a920 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -153,7 +153,6 @@
 
     private Button mQuickFixButton;
     private QuickFix mQuickFix;
-    private final ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
     private int mNumPhoneNumbers = 0;
     private String mDefaultCountryIso;
     private boolean mContactHasSocialUpdates;
@@ -530,8 +529,6 @@
         mPrimaryPhoneUri = null;
         mNumPhoneNumbers = 0;
 
-        mWritableRawContactIds.clear();
-
         final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
 
         // Build up method entries
@@ -549,10 +546,8 @@
             if (!mRawContactIds.contains(rawContactId)) {
                 mRawContactIds.add(rawContactId);
             }
+
             AccountType type = accountTypes.getAccountType(accountType, dataSet);
-            if (type == null || type.areContactsWritable()) {
-                mWritableRawContactIds.add(rawContactId);
-            }
 
             for (NamedContentValues subValue : entity.getSubValues()) {
                 final ContentValues entryValues = subValue.values;
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 22fc3fc..a8c0b36 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -95,7 +95,7 @@
 public class ContactEditorFragment extends Fragment implements
         SplitContactConfirmationDialogFragment.Listener,
         AggregationSuggestionEngine.Listener, AggregationSuggestionView.Listener,
-        ExternalRawContactEditorView.Listener {
+        RawContactReadOnlyEditorView.Listener {
 
     private static final String TAG = ContactEditorFragment.class.getSimpleName();
 
@@ -449,7 +449,8 @@
             String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
             AccountType accountType = AccountTypeManager.getInstance(mContext).getAccountType(
                     type, dataSet);
-            if (accountType.getEditContactActivityClassName() != null) {
+            if (accountType.getEditContactActivityClassName() != null &&
+                    !accountType.areContactsWritable()) {
                 if (mListener != null) {
                     String name = entityValues.getAsString(RawContacts.ACCOUNT_NAME);
                     long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
@@ -671,10 +672,10 @@
             final long rawContactId = values.getAsLong(RawContacts._ID);
 
             final BaseRawContactEditorView editor;
-            if (type.isExternal()) {
+            if (!type.areContactsWritable()) {
                 editor = (BaseRawContactEditorView) inflater.inflate(
-                        R.layout.external_raw_contact_editor_view, mContent, false);
-                ((ExternalRawContactEditorView) editor).setListener(this);
+                        R.layout.raw_contact_readonly_editor_view, mContent, false);
+                ((RawContactReadOnlyEditorView) editor).setListener(this);
             } else {
                 editor = (RawContactEditorView) inflater.inflate(R.layout.raw_contact_editor_view,
                         mContent, false);
diff --git a/src/com/android/contacts/editor/ExternalRawContactEditorView.java b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
similarity index 97%
rename from src/com/android/contacts/editor/ExternalRawContactEditorView.java
rename to src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
index b95a742..2cc5d98 100644
--- a/src/com/android/contacts/editor/ExternalRawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
@@ -51,7 +51,7 @@
 /**
  * Custom view that displays external contacts in the edit screen.
  */
-public class ExternalRawContactEditorView extends BaseRawContactEditorView
+public class RawContactReadOnlyEditorView extends BaseRawContactEditorView
         implements OnClickListener {
     private LayoutInflater mInflater;
 
@@ -76,11 +76,11 @@
         void onExternalEditorRequest(AccountWithDataSet account, Uri uri);
     }
 
-    public ExternalRawContactEditorView(Context context) {
+    public RawContactReadOnlyEditorView(Context context) {
         super(context);
     }
 
-    public ExternalRawContactEditorView(Context context, AttributeSet attrs) {
+    public RawContactReadOnlyEditorView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
@@ -191,7 +191,7 @@
         mName.setText(primary != null ? primary.getAsString(StructuredName.DISPLAY_NAME) :
                 mContext.getString(R.string.missing_name));
 
-        if (type.areContactsWritable()) {
+        if (type.getEditContactActivityClassName() != null) {
             mAccountContainer.setBackgroundDrawable(null);
             mAccountContainer.setEnabled(false);
             mEditExternallyButton.setVisibility(View.VISIBLE);
@@ -241,7 +241,8 @@
                 } else {
                     emailType = null;
                 }
-                bindData(mContext.getText(R.string.emailLabelsGroup), emailAddress, null, i == 0);
+                bindData(mContext.getText(R.string.emailLabelsGroup), emailAddress, emailType,
+                        i == 0);
             }
         }
 
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index eef8049..21e17bd 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -81,16 +81,14 @@
      */
     private HashMap<String, DataKind> mMimeKinds = Maps.newHashMap();
 
-    public boolean isExternal() {
-        return false;
-    }
-
     public boolean isExtension() {
         return false;
     }
 
     /**
-     * @return True if contacts can be created and edited using this app
+     * @return True if contacts can be created and edited using this app. If false,
+     * there could still be an external editor as provided by
+     * {@link #getEditContactActivityClassName()} or {@link #getCreateContactActivityClassName()}
      */
     public abstract boolean areContactsWritable();
 
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index fdac645..8582ed8 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -961,12 +961,11 @@
             // Migrate data supported by the new account type.
             // All the other data inside oldState are silently dropped.
             for (DataKind kind : newAccountType.getSortedDataKinds()) {
+                if (!kind.editable) continue;
                 final String mimeType = kind.mimeType;
-                final int fieldCount = kind.fieldList.size();
-                final Set<String> allowedColumns = new HashSet<String>();
                 if (DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)
                         || DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(mimeType)) {
-                    // Ignore pseude data.
+                    // Ignore pseudo data.
                     continue;
                 } else if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
                     migrateStructuredName(context, oldState, newState, kind);
@@ -976,8 +975,10 @@
                     migrateEvent(oldState, newState, kind, null /* default Year */);
                 } else if (sGenericMimeTypesWithoutTypeSupport.contains(mimeType)) {
                     migrateGenericWithoutTypeColumn(oldState, newState, kind);
-                } else {
+                } else if (sGenericMimeTypesWithTypeSupport.contains(mimeType)) {
                     migrateGenericWithTypeColumn(oldState, newState, kind);
+                } else {
+                    throw new IllegalStateException("Unexpected editable mime-type: " + mimeType);
                 }
             }
         }
@@ -1296,10 +1297,6 @@
     /** @hide Public only for testing. */
     public static void migrateGenericWithTypeColumn(
             EntityDelta oldState, EntityDelta newState, DataKind newDataKind) {
-        if (!sGenericMimeTypesWithTypeSupport.contains(newDataKind.mimeType)) {
-            throw new RuntimeException("not supported: " + newDataKind.mimeType);
-        }
-
         final ArrayList<ValuesDelta> mimeEntries = oldState.getMimeEntries(newDataKind.mimeType);
         if (mimeEntries == null || mimeEntries.isEmpty()) {
             return;
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index 9718ce2..ca064c7 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -49,6 +49,7 @@
     private static final String TAG_CONTACTS_SOURCE_LEGACY = "ContactsSource";
     private static final String TAG_CONTACTS_ACCOUNT_TYPE = "ContactsAccountType";
     private static final String TAG_CONTACTS_DATA_KIND = "ContactsDataKind";
+    private static final String TAG_EDIT_SCHEMA = "EditSchema";
 
     private static final String ATTR_EDIT_CONTACT_ACTIVITY = "editContactActivity";
     private static final String ATTR_CREATE_CONTACT_ACTIVITY = "createContactActivity";
@@ -84,6 +85,7 @@
     private String mAccountTypeIconAttribute;
     private boolean mInitSuccessful;
     private boolean mHasContactsMetadata;
+    private boolean mHasEditSchema;
 
     public ExternalAccountType(Context context, String resPackageName, boolean isExtension) {
         this.mIsExtension = isExtension;
@@ -125,11 +127,6 @@
     }
 
     @Override
-    public boolean isExternal() {
-        return true;
-    }
-
-    @Override
     public boolean isExtension() {
         return mIsExtension;
     }
@@ -144,7 +141,7 @@
 
     @Override
     public boolean areContactsWritable() {
-        return getCreateContactActivityClassName() != null;
+        return mHasEditSchema;
     }
 
     /**
@@ -269,44 +266,45 @@
             while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
                     && type != XmlPullParser.END_DOCUMENT) {
                 String tag = parser.getName();
-                if (type == XmlPullParser.END_TAG || !TAG_CONTACTS_DATA_KIND.equals(tag)) {
-                    continue;
+                if (TAG_EDIT_SCHEMA.equals(tag)) {
+                    parseEditSchema(context, parser);
+                } else if (TAG_CONTACTS_DATA_KIND.equals(tag)) {
+                    final TypedArray a = context.obtainStyledAttributes(attrs,
+                            android.R.styleable.ContactsDataKind);
+                    final DataKind kind = new DataKind();
+
+                    kind.mimeType = a
+                            .getString(com.android.internal.R.styleable.ContactsDataKind_mimeType);
+                    kind.iconRes = a.getResourceId(
+                            com.android.internal.R.styleable.ContactsDataKind_icon, -1);
+
+                    final String summaryColumn = a.getString(
+                            com.android.internal.R.styleable.ContactsDataKind_summaryColumn);
+                    if (summaryColumn != null) {
+                        // Inflate a specific column as summary when requested
+                        kind.actionHeader = new SimpleInflater(summaryColumn);
+                    }
+
+                    final String detailColumn = a.getString(
+                            com.android.internal.R.styleable.ContactsDataKind_detailColumn);
+                    final boolean detailSocialSummary = a.getBoolean(
+                            com.android.internal.R.styleable.ContactsDataKind_detailSocialSummary,
+                            false);
+
+                    if (detailSocialSummary) {
+                        // Inflate social summary when requested
+                        kind.actionBodySocial = true;
+                    }
+
+                    if (detailColumn != null) {
+                        // Inflate specific column as summary
+                        kind.actionBody = new SimpleInflater(detailColumn);
+                    }
+
+                    a.recycle();
+
+                    addKind(kind);
                 }
-
-                final TypedArray a = context.obtainStyledAttributes(attrs,
-                        android.R.styleable.ContactsDataKind);
-                final DataKind kind = new DataKind();
-
-                kind.mimeType = a
-                        .getString(com.android.internal.R.styleable.ContactsDataKind_mimeType);
-                kind.iconRes = a.getResourceId(
-                        com.android.internal.R.styleable.ContactsDataKind_icon, -1);
-
-                final String summaryColumn = a
-                        .getString(com.android.internal.R.styleable.ContactsDataKind_summaryColumn);
-                if (summaryColumn != null) {
-                    // Inflate a specific column as summary when requested
-                    kind.actionHeader = new FallbackAccountType.SimpleInflater(summaryColumn);
-                }
-
-                final String detailColumn = a
-                        .getString(com.android.internal.R.styleable.ContactsDataKind_detailColumn);
-                final boolean detailSocialSummary = a.getBoolean(
-                        com.android.internal.R.styleable.ContactsDataKind_detailSocialSummary,
-                        false);
-
-                if (detailSocialSummary) {
-                    // Inflate social summary when requested
-                    kind.actionBodySocial = true;
-                }
-
-                if (detailColumn != null) {
-                    // Inflate specific column as summary
-                    kind.actionBody = new FallbackAccountType.SimpleInflater(detailColumn);
-                }
-
-                addKind(kind);
-                a.recycle();
             }
         } catch (XmlPullParserException e) {
             throw new IllegalStateException("Problem reading XML", e);
@@ -315,6 +313,36 @@
         }
     }
 
+    /**
+     * Has to be started while the parser is on the EditSchema tag. Will finish on the end tag
+     */
+    private void parseEditSchema(Context context, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        // Loop until we left this tag
+        final int startingDepth = parser.getDepth();
+        int type;
+        do {
+            type = parser.next();
+        } while (!(parser.getDepth() == startingDepth && type == XmlPullParser.END_TAG));
+
+        // Just add all defaults for now
+        addDataKindStructuredName(context);
+        addDataKindDisplayName(context);
+        addDataKindPhoneticName(context);
+        addDataKindNickname(context);
+        addDataKindPhone(context);
+        addDataKindEmail(context);
+        addDataKindStructuredPostal(context);
+        addDataKindIm(context);
+        addDataKindOrganization(context);
+        addDataKindPhoto(context);
+        addDataKindNote(context);
+        addDataKindWebsite(context);
+        addDataKindSipAddress(context);
+
+        mHasEditSchema = true;
+    }
+
     @Override
     public int getHeaderColor(Context context) {
         return 0xff6d86b4;
diff --git a/src/com/android/contacts/util/Constants.java b/src/com/android/contacts/util/Constants.java
index d79f029..3a43c40 100644
--- a/src/com/android/contacts/util/Constants.java
+++ b/src/com/android/contacts/util/Constants.java
@@ -25,7 +25,27 @@
     public static final String SCHEME_IMTO = "imto";
     public static final String SCHEME_SIP = "sip";
 
-    // Log tag for performance measurement.
-    // To enable: adb shell setprop log.tag.ContactsPerf VERBOSE
+    /**
+     * Log tag for performance measurement.
+     * To enable: adb shell setprop log.tag.ContactsPerf VERBOSE
+     */
     public static final String PERFORMANCE_TAG = "ContactsPerf";
+
+    /**
+     * Log tag for enabling/disabling LoaderManager log.
+     * To enable: adb shell setprop log.tag.ContactsLoaderManager DEBUG
+     */
+    public static final String LOADER_MANAGER_TAG = "ContactsLoaderManager";
+
+    /**
+     * Log tag for enabling/disabling FragmentManager log.
+     * To enable: adb shell setprop log.tag.ContactsFragmentManager DEBUG
+     */
+    public static final String FRAGMENT_MANAGER_TAG = "ContactsFragmentManager";
+
+    /**
+     * Log tag for enabling/disabling StrictMode violation log.
+     * To enable: adb shell setprop log.tag.ContactsStrictMode DEBUG
+     */
+    public static final String STRICT_MODE_TAG = "ContactsStrictMode";
 }