Merge "Suppress too verbose logging"
diff --git a/res/layout/call_detail.xml b/res/layout/call_detail.xml
index a890ddf..13124f2 100644
--- a/res/layout/call_detail.xml
+++ b/res/layout/call_detail.xml
@@ -79,16 +79,6 @@
             android:background="@android:color/holo_blue_light"
             android:layout_below="@+id/contact_background_sizer"
         />
-        <LinearLayout
-            android:id="@+id/voicemail_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingBottom="@dimen/call_detail_button_spacing"
-            android:layout_below="@id/blue_separator"
-            android:background="@android:color/black"
-        >
-            <!-- The voicemail fragment will be put here. -->
-        </LinearLayout>
         <View
             android:id="@+id/photo_text_bar"
             android:layout_width="match_parent"
@@ -132,6 +122,16 @@
             android:layout_alignBottom="@id/contact_background_sizer"
             android:background="?android:attr/selectableItemBackground"
         />
+        <LinearLayout
+            android:id="@+id/voicemail_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="@dimen/call_detail_button_spacing"
+            android:layout_below="@id/blue_separator"
+            android:background="@android:color/black"
+        >
+            <!-- The voicemail fragment will be put here. -->
+        </LinearLayout>
         <FrameLayout android:id="@+id/call_and_sms_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
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/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index b7ccffc..8d7561a 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -507,7 +507,8 @@
                 ListView historyList = (ListView) findViewById(R.id.history);
                 historyList.setAdapter(
                         new CallDetailHistoryAdapter(CallDetailActivity.this, mInflater,
-                                mCallTypeHelper, details, hasVoicemail(), canPlaceCallsTo));
+                                mCallTypeHelper, details, hasVoicemail(), canPlaceCallsTo,
+                                findViewById(R.id.controls)));
                 BackScrollManager.bind(
                         new ScrollableHeader() {
                             private View controls = findViewById(R.id.controls);
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/calllog/CallDetailHistoryAdapter.java b/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
index ed9c2e0..22b85d7 100644
--- a/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
+++ b/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
@@ -45,16 +45,30 @@
     private final boolean mShowVoicemail;
     /** Whether the call and SMS controls are shown. */
     private final boolean mShowCallAndSms;
+    /** The controls that are shown on top of the history list. */
+    private final View mControls;
+    /** The listener to changes of focus of the header. */
+    private View.OnFocusChangeListener mHeaderFocusChangeListener =
+            new View.OnFocusChangeListener() {
+        @Override
+        public void onFocusChange(View v, boolean hasFocus) {
+            // When the header is focused, focus the controls above it instead.
+            if (hasFocus) {
+                mControls.requestFocus();
+            }
+        }
+    };
 
     public CallDetailHistoryAdapter(Context context, LayoutInflater layoutInflater,
             CallTypeHelper callTypeHelper, PhoneCallDetails[] phoneCallDetails,
-            boolean showVoicemail, boolean showCallAndSms) {
+            boolean showVoicemail, boolean showCallAndSms, View controls) {
         mContext = context;
         mLayoutInflater = layoutInflater;
         mCallTypeHelper = callTypeHelper;
         mPhoneCallDetails = phoneCallDetails;
         mShowVoicemail = showVoicemail;
         mShowCallAndSms = showCallAndSms;
+        mControls = controls;
     }
 
     @Override
@@ -103,6 +117,8 @@
             // Call and SMS controls are only shown in the main UI if there is a known number.
             View callAndSmsContainer = header.findViewById(R.id.header_call_and_sms_container);
             callAndSmsContainer.setVisibility(mShowCallAndSms ? View.VISIBLE : View.GONE);
+            header.setFocusable(true);
+            header.setOnFocusChangeListener(mHeaderFocusChangeListener);
             return header;
         }
 
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 456abe8..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.readOnly) {
-                mWritableRawContactIds.add(rawContactId);
-            }
 
             for (NamedContentValues subValue : entity.getSubValues()) {
                 final ContentValues entryValues = subValue.values;
@@ -1948,7 +1943,7 @@
                     AccountTypeManager.getInstance(mContext);
             final AccountType type = accountTypes.getAccountType(accountType, dataSet);
             // Offline or non-writeable account? Nothing to fix
-            if (type == null || type.readOnly) return false;
+            if (type == null || !type.areContactsWritable()) return false;
 
             // Check whether the contact is in the default group
             boolean isInDefaultGroup = false;
diff --git a/src/com/android/contacts/editor/AggregationSuggestionView.java b/src/com/android/contacts/editor/AggregationSuggestionView.java
index 996dbc4..9d7e16a 100644
--- a/src/com/android/contacts/editor/AggregationSuggestionView.java
+++ b/src/com/android/contacts/editor/AggregationSuggestionView.java
@@ -120,7 +120,7 @@
                 return true;
             }
             AccountType type = accountTypes.getAccountType(accountType, dataSet);
-            if (!type.readOnly) {
+            if (type.areContactsWritable()) {
                 return true;
             }
         }
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 2e54642..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);
@@ -519,7 +520,7 @@
             final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
             final String dataSet = state.getValues().getAsString(RawContacts.DATA_SET);
             final AccountType type = accountTypes.getAccountType(accountType, dataSet);
-            if (!type.readOnly) {
+            if (type.areContactsWritable()) {
                 // Apply extras to the first writable raw contact only
                 EntityModifier.parseExtras(mContext, type, state, extras);
                 break;
@@ -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);
@@ -698,7 +699,7 @@
             editor.setState(entity, type, mViewIdGenerator, isEditingUserProfile());
 
             editor.getPhotoEditor().setEditorListener(
-                    new PhotoEditorListener(editor, type.readOnly));
+                    new PhotoEditorListener(editor, type.areContactsWritable()));
             if (editor instanceof RawContactEditorView) {
                 final RawContactEditorView rawContactEditor = (RawContactEditorView) editor;
                 EditorListener listener = new EditorListener() {
@@ -1154,7 +1155,7 @@
             final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
             final String dataSet = values.getAsString(RawContacts.DATA_SET);
             final AccountType type = accountTypes.getAccountType(accountType, dataSet);
-            if (!type.readOnly) {
+            if (type.areContactsWritable()) {
                 return true;
             }
         }
@@ -1234,9 +1235,9 @@
             final AccountType type2 = accountTypes.getAccountType(accountType2, dataSet2);
 
             // Check read-only
-            if (type1.readOnly && !type2.readOnly) {
+            if (!type1.areContactsWritable() && type2.areContactsWritable()) {
                 return 1;
-            } else if (!type1.readOnly && type2.readOnly) {
+            } else if (type1.areContactsWritable() && !type2.areContactsWritable()) {
                 return -1;
             }
 
@@ -1693,11 +1694,11 @@
     private final class PhotoEditorListener
             implements EditorListener, PhotoActionPopup.Listener {
         private final BaseRawContactEditorView mEditor;
-        private final boolean mAccountReadOnly;
+        private final boolean mAccountWritable;
 
-        private PhotoEditorListener(BaseRawContactEditorView editor, boolean accountReadOnly) {
+        private PhotoEditorListener(BaseRawContactEditorView editor, boolean accountWritable) {
             mEditor = editor;
-            mAccountReadOnly = accountReadOnly;
+            mAccountWritable = accountWritable;
         }
 
         @Override
@@ -1707,14 +1708,7 @@
             if (request == EditorListener.REQUEST_PICK_PHOTO) {
                 // Determine mode
                 final int mode;
-                if (mAccountReadOnly) {
-                    if (mEditor.hasSetPhoto() && hasMoreThanOnePhoto()) {
-                        mode = PhotoActionPopup.MODE_READ_ONLY_ALLOW_PRIMARY;
-                    } else {
-                        // Read-only and either no photo or the only photo ==> no options
-                        return;
-                    }
-                } else {
+                if (mAccountWritable) {
                     if (mEditor.hasSetPhoto()) {
                         if (hasMoreThanOnePhoto()) {
                             mode = PhotoActionPopup.MODE_PHOTO_ALLOW_PRIMARY;
@@ -1724,6 +1718,13 @@
                     } else {
                         mode = PhotoActionPopup.MODE_NO_PHOTO;
                     }
+                } else {
+                    if (mEditor.hasSetPhoto() && hasMoreThanOnePhoto()) {
+                        mode = PhotoActionPopup.MODE_READ_ONLY_ALLOW_PRIMARY;
+                    } else {
+                        // Read-only and either no photo or the only photo ==> no options
+                        return;
+                    }
                 }
                 PhotoActionPopup.createPopupMenu(mContext, mEditor.getPhotoEditor(), this, mode)
                         .show();
diff --git a/src/com/android/contacts/editor/ExternalRawContactEditorView.java b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
similarity index 96%
rename from src/com/android/contacts/editor/ExternalRawContactEditorView.java
rename to src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
index 0788961..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);
     }
 
@@ -176,7 +176,7 @@
             boolean hasPhotoEditor = type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null;
             setHasPhotoEditor(hasPhotoEditor);
             primary = state.getPrimaryEntry(Photo.CONTENT_ITEM_TYPE);
-            getPhotoEditor().setValues(kind, primary, state, type.readOnly, vig);
+            getPhotoEditor().setValues(kind, primary, state, !type.areContactsWritable(), vig);
             if (!hasPhotoEditor || !getPhotoEditor().hasSetPhoto()) {
                 mPhotoStub.setVisibility(View.GONE);
             } else {
@@ -191,7 +191,11 @@
         mName.setText(primary != null ? primary.getAsString(StructuredName.DISPLAY_NAME) :
                 mContext.getString(R.string.missing_name));
 
-        if (type.readOnly) {
+        if (type.getEditContactActivityClassName() != null) {
+            mAccountContainer.setBackgroundDrawable(null);
+            mAccountContainer.setEnabled(false);
+            mEditExternallyButton.setVisibility(View.VISIBLE);
+        } else {
             mAccountContainer.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
@@ -200,10 +204,6 @@
                 }
             });
             mEditExternallyButton.setVisibility(View.GONE);
-        } else {
-            mAccountContainer.setBackgroundDrawable(null);
-            mAccountContainer.setEnabled(false);
-            mEditExternallyButton.setVisibility(View.VISIBLE);
         }
 
         final Resources res = mContext.getResources();
@@ -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/interactions/ContactDeletionInteraction.java b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
index 86f4eda..7e880a4 100644
--- a/src/com/android/contacts/interactions/ContactDeletionInteraction.java
+++ b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
@@ -240,11 +240,11 @@
             contactId = cursor.getLong(COLUMN_INDEX_CONTACT_ID);
             lookupKey = cursor.getString(COLUMN_INDEX_LOOKUP_KEY);
             AccountType type = accountTypes.getAccountType(accountType, dataSet);
-            boolean readonly = type != null && type.readOnly;
-            if (readonly) {
-                readOnlyRawContacts.add(rawContactId);
-            } else {
+            boolean writable = type == null || type.areContactsWritable();
+            if (writable) {
                 writableRawContacts.add(rawContactId);
+            } else {
+                readOnlyRawContacts.add(rawContactId);
             }
         }
 
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 879a89f..21e17bd 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -71,8 +71,6 @@
     public int titleRes;
     public int iconRes;
 
-    public boolean readOnly;
-
     /**
      * Set of {@link DataKind} supported by this source.
      */
@@ -83,15 +81,18 @@
      */
     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. If false,
+     * there could still be an external editor as provided by
+     * {@link #getEditContactActivityClassName()} or {@link #getCreateContactActivityClassName()}
+     */
+    public abstract boolean areContactsWritable();
+
+    /**
      * Returns an optional custom edit activity.  The activity class should reside
      * in the sync adapter package as determined by {@link #resPackageName}.
      */
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index 33d6c97..bdd8a50 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -329,7 +329,6 @@
                         // Skip external account types that couldn't be initialized.
                         continue;
                     }
-                    accountType.readOnly = !sync.supportsUploading();
                 }
 
                 accountType.accountType = auth.type;
@@ -395,7 +394,7 @@
                         AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
                                 account.name, account.type, accountType.dataSet);
                         allAccounts.add(accountWithDataSet);
-                        if (!accountType.readOnly) {
+                        if (accountType.areContactsWritable()) {
                             writableAccounts.add(accountWithDataSet);
                         }
                     }
diff --git a/src/com/android/contacts/model/BaseAccountType.java b/src/com/android/contacts/model/BaseAccountType.java
index e6c0400..b599c66 100644
--- a/src/com/android/contacts/model/BaseAccountType.java
+++ b/src/com/android/contacts/model/BaseAccountType.java
@@ -40,7 +40,7 @@
 import android.provider.ContactsContract.CommonDataKinds.Website;
 import android.view.inputmethod.EditorInfo;
 
-public class BaseAccountType extends AccountType {
+public abstract class BaseAccountType extends AccountType {
     protected static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE;
     protected static final int FLAGS_EMAIL = EditorInfo.TYPE_CLASS_TEXT
             | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
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/ExchangeAccountType.java b/src/com/android/contacts/model/ExchangeAccountType.java
index 240df47..4a0e7a0 100644
--- a/src/com/android/contacts/model/ExchangeAccountType.java
+++ b/src/com/android/contacts/model/ExchangeAccountType.java
@@ -339,4 +339,9 @@
     public boolean isGroupMembershipEditable() {
         return true;
     }
+
+    @Override
+    public boolean areContactsWritable() {
+        return true;
+    }
 }
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index 7fefc44..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";
@@ -65,7 +66,6 @@
     // The following attributes should only be set in non-sync-adapter account types.  They allow
     // for the account type and resource IDs to be specified without an associated authenticator.
     private static final String ATTR_ACCOUNT_TYPE = "accountType";
-    private static final String ATTR_READ_ONLY = "readOnly";
     private static final String ATTR_ACCOUNT_LABEL = "accountTypeLabel";
     private static final String ATTR_ACCOUNT_ICON = "accountTypeIcon";
 
@@ -85,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;
@@ -126,11 +127,6 @@
     }
 
     @Override
-    public boolean isExternal() {
-        return true;
-    }
-
-    @Override
     public boolean isExtension() {
         return mIsExtension;
     }
@@ -143,6 +139,11 @@
         return mInitSuccessful;
     }
 
+    @Override
+    public boolean areContactsWritable() {
+        return mHasEditSchema;
+    }
+
     /**
      * Whether this account type has the android.provider.CONTACTS_STRUCTURE metadata xml.
      */
@@ -251,8 +252,6 @@
                     mExtensionPackageNames.add(value);
                 } else if (ATTR_ACCOUNT_TYPE.equals(attr)) {
                     accountType = value;
-                } else if (ATTR_READ_ONLY.equals(attr)) {
-                    readOnly = !"0".equals(value) && !"false".equals(value);
                 } else if (ATTR_ACCOUNT_LABEL.equals(attr)) {
                     mAccountTypeLabelAttribute = value;
                 } else if (ATTR_ACCOUNT_ICON.equals(attr)) {
@@ -267,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);
@@ -313,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/model/FallbackAccountType.java b/src/com/android/contacts/model/FallbackAccountType.java
index 8bb3992..3b56b04 100644
--- a/src/com/android/contacts/model/FallbackAccountType.java
+++ b/src/com/android/contacts/model/FallbackAccountType.java
@@ -55,4 +55,9 @@
     public int getSideBarColor(Context context) {
         return 0xffbdc7b8;
     }
+
+    @Override
+    public boolean areContactsWritable() {
+        return true;
+    }
 }
diff --git a/src/com/android/contacts/model/GoogleAccountType.java b/src/com/android/contacts/model/GoogleAccountType.java
index a5fab96..cee43dd 100644
--- a/src/com/android/contacts/model/GoogleAccountType.java
+++ b/src/com/android/contacts/model/GoogleAccountType.java
@@ -183,4 +183,9 @@
     public boolean isGroupMembershipEditable() {
         return true;
     }
+
+    @Override
+    public boolean areContactsWritable() {
+        return true;
+    }
 }
diff --git a/tests/src/com/android/contacts/ContactLoaderTest.java b/tests/src/com/android/contacts/ContactLoaderTest.java
index 5d44cf1..f88b64e 100644
--- a/tests/src/com/android/contacts/ContactLoaderTest.java
+++ b/tests/src/com/android/contacts/ContactLoaderTest.java
@@ -51,7 +51,12 @@
         mContactsProvider = mMockContext.getContactsProvider();
 
         InjectedServices services = new InjectedServices();
-        AccountType accountType = new BaseAccountType();
+        AccountType accountType = new BaseAccountType() {
+            @Override
+            public boolean areContactsWritable() {
+                return false;
+            }
+        };
         accountType.accountType = "mockAccountType";
 
         AccountWithDataSet account =
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
index cf6fefe..76d3d84 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -153,6 +153,11 @@
         public boolean isGroupMembershipEditable() {
             return false;
         }
+
+        @Override
+        public boolean areContactsWritable() {
+            return true;
+        }
     }
 
     /**
diff --git a/tests/src/com/android/contacts/activities/PeopleActivityTest.java b/tests/src/com/android/contacts/activities/PeopleActivityTest.java
index f419842..66c2c5a 100644
--- a/tests/src/com/android/contacts/activities/PeopleActivityTest.java
+++ b/tests/src/com/android/contacts/activities/PeopleActivityTest.java
@@ -94,7 +94,12 @@
         services.setSharedPreferences(new MockSharedPreferences());
         services.setSystemService(ContactPhotoManager.CONTACT_PHOTO_SERVICE,
                 new MockContactPhotoManager());
-        AccountType accountType = new BaseAccountType();
+        AccountType accountType = new BaseAccountType() {
+            @Override
+            public boolean areContactsWritable() {
+                return false;
+            }
+        };
         accountType.accountType = TEST_ACCOUNT_TYPE;
 
         AccountWithDataSet account = new AccountWithDataSet(TEST_ACCOUNT, TEST_ACCOUNT_TYPE, null);
diff --git a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
index a6222db..2c4b74c 100644
--- a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
+++ b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
@@ -83,11 +83,20 @@
         InjectedServices services = new InjectedServices();
         services.setContentResolver(mContext.getContentResolver());
 
-        AccountType readOnlyAccountType = new BaseAccountType();
+        AccountType readOnlyAccountType = new BaseAccountType() {
+            @Override
+            public boolean areContactsWritable() {
+                return false;
+            }
+        };
         readOnlyAccountType.accountType = READONLY_ACCOUNT_TYPE;
-        readOnlyAccountType.readOnly = true;
 
-        AccountType writableAccountType = new BaseAccountType();
+        AccountType writableAccountType = new BaseAccountType() {
+            @Override
+            public boolean areContactsWritable() {
+                return true;
+            }
+        };
         writableAccountType.accountType = WRITABLE_ACCOUNT_TYPE;
 
         services.setSystemService(AccountTypeManager.ACCOUNT_TYPE_SERVICE,
diff --git a/tests/src/com/android/contacts/model/AccountTypeManagerTest.java b/tests/src/com/android/contacts/model/AccountTypeManagerTest.java
index 81c270f..aadf411 100644
--- a/tests/src/com/android/contacts/model/AccountTypeManagerTest.java
+++ b/tests/src/com/android/contacts/model/AccountTypeManagerTest.java
@@ -196,5 +196,10 @@
         public boolean isGroupMembershipEditable() {
             return false;
         }
+
+        @Override
+        public boolean areContactsWritable() {
+            return false;
+        }
     }
 }
diff --git a/tests/src/com/android/contacts/model/AccountTypeTest.java b/tests/src/com/android/contacts/model/AccountTypeTest.java
index 3d80b52..9f7e7a2 100644
--- a/tests/src/com/android/contacts/model/AccountTypeTest.java
+++ b/tests/src/com/android/contacts/model/AccountTypeTest.java
@@ -84,6 +84,10 @@
             @Override public boolean isGroupMembershipEditable() {
                 return false;
             }
+
+            @Override public boolean areContactsWritable() {
+                return false;
+            }
         };
 
         assertEquals(getTestContext().getString(externalResID),
@@ -137,5 +141,10 @@
         public boolean isGroupMembershipEditable() {
             return false;
         }
+
+        @Override
+        public boolean areContactsWritable() {
+            return false;
+        }
     }
 }