Merge "Add CompatUtils#isNCompatible()" into ub-contactsdialer-b-dev
diff --git a/src/com/android/contacts/common/model/account/BaseAccountType.java b/src/com/android/contacts/common/model/account/BaseAccountType.java
index 7526f0c..214ba77 100644
--- a/src/com/android/contacts/common/model/account/BaseAccountType.java
+++ b/src/com/android/contacts/common/model/account/BaseAccountType.java
@@ -119,7 +119,7 @@
         this.accountType = null;
         this.dataSet = null;
         this.titleRes = R.string.account_phone;
-        this.iconRes = R.mipmap.ic_contacts_clr_48cv_44dp;
+        this.iconRes = R.mipmap.ic_contacts_launcher;
     }
 
     protected static EditType buildPhoneType(int type) {
diff --git a/src/com/android/contacts/common/model/account/FallbackAccountType.java b/src/com/android/contacts/common/model/account/FallbackAccountType.java
index fd81e46..ce1d059 100644
--- a/src/com/android/contacts/common/model/account/FallbackAccountType.java
+++ b/src/com/android/contacts/common/model/account/FallbackAccountType.java
@@ -30,7 +30,7 @@
         this.accountType = null;
         this.dataSet = null;
         this.titleRes = R.string.account_phone;
-        this.iconRes = R.mipmap.ic_contacts_clr_48cv_44dp;
+        this.iconRes = R.mipmap.ic_contacts_launcher;
 
         // Note those are only set for unit tests.
         this.resourcePackageName = resPackageName;
diff --git a/src/com/android/contacts/common/preference/ContactMetadataSyncAccountPreference.java b/src/com/android/contacts/common/preference/ContactMetadataSyncAccountPreference.java
new file mode 100644
index 0000000..37d45f5
--- /dev/null
+++ b/src/com/android/contacts/common/preference/ContactMetadataSyncAccountPreference.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 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.common.preference;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.preference.ListPreference;
+import android.util.AttributeSet;
+
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountTypeWithDataSet;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.model.account.GoogleAccountType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ContactMetadataSyncAccountPreference extends ListPreference {
+    private ContactsPreferences mPreferences;
+    private Map<String, AccountWithDataSet> mAccountMap;
+
+    public ContactMetadataSyncAccountPreference(Context context) {
+        super(context);
+        prepare();
+    }
+
+    public ContactMetadataSyncAccountPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        prepare();
+    }
+
+    private void prepare() {
+        mPreferences = new ContactsPreferences(getContext());
+        mAccountMap = new HashMap<>();
+        final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(getContext());
+        List<AccountWithDataSet> accounts = accountTypeManager.getAccounts(true);
+        mAccountMap.put(ContactsPreferences.DO_NOT_SYNC_CONTACT_METADATA_MSG, null);
+        for (AccountWithDataSet account : accounts) {
+            if (GoogleAccountType.ACCOUNT_TYPE.equals(account.type) && account.dataSet == null) {
+                mAccountMap.put(account.name, account);
+            }
+        }
+        final Set<String> accountNames = mAccountMap.keySet();
+        final String[] accountNamesArray = accountNames.toArray(new String[accountNames.size()]);
+        setEntries(accountNamesArray);
+        setEntryValues(accountNamesArray);
+        setValue(mPreferences.getContactMetadataSyncAccount());
+    }
+
+    @Override
+    protected boolean shouldPersist() {
+        return false;   // This preference takes care of its own storage
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        return mPreferences.getContactMetadataSyncAccount();
+    }
+
+    @Override
+    protected boolean persistString(String value) {
+        if (value == null && mPreferences.getContactMetadataSyncAccount() == null) {
+            return true;
+        }
+        if (value == null || mPreferences.getContactMetadataSyncAccount() == null
+                || !value.equals(mPreferences.getContactMetadataSyncAccount())) {
+            mPreferences.setContactMetadataSyncAccount(mAccountMap.get(value));
+            notifyChanged();
+        }
+        return true;
+    }
+
+    @Override
+    // UX recommendation is not to show cancel button on such lists.
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+        super.onPrepareDialogBuilder(builder);
+        builder.setNegativeButton(null, null);
+    }
+}
diff --git a/src/com/android/contacts/common/preference/ContactsPreferences.java b/src/com/android/contacts/common/preference/ContactsPreferences.java
index ca38af7..37448e3 100644
--- a/src/com/android/contacts/common/preference/ContactsPreferences.java
+++ b/src/com/android/contacts/common/preference/ContactsPreferences.java
@@ -16,18 +16,28 @@
 
 package com.android.contacts.common.preference;
 
+import android.accounts.Account;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.preference.PreferenceManager;
+import android.provider.ContactsContract;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
 
 import com.android.contacts.common.R;
 import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.model.account.GoogleAccountType;
+import com.android.contacts.common.model.AccountTypeManager;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Manages user preferences for contacts.
@@ -62,6 +72,10 @@
 
     public static final boolean PREF_DISPLAY_ONLY_PHONES_DEFAULT = false;
 
+    public static final String DO_NOT_SYNC_CONTACT_METADATA_MSG = "Do not sync contact metadata.";
+
+    public static final String CONTACT_METADATA_AUTHORITY = "com.android.contacts.metadata";
+
     /**
      * Value to use when a preference is unassigned and needs to be read from the shared preferences
      */
@@ -179,6 +193,80 @@
         editor.commit();
     }
 
+    public String getContactMetadataSyncAccount() {
+        for (Account account : getFocusGoogleAccounts()) {
+            if (ContentResolver.getIsSyncable(account, CONTACT_METADATA_AUTHORITY) == 1
+                    && ContentResolver.getSyncAutomatically(account, CONTACT_METADATA_AUTHORITY)) {
+                return account.name;
+            }
+        }
+        return DO_NOT_SYNC_CONTACT_METADATA_MSG;
+    }
+
+    public void setContactMetadataSyncAccount(AccountWithDataSet accountWithDataSet) {
+        final String mContactMetadataSyncAccount =
+                accountWithDataSet == null ? null : accountWithDataSet.name;
+        toggleContactMetadata(mContactMetadataSyncAccount);
+    }
+
+    private void toggleContactMetadata(String syncAccount) {
+        mContext.getContentResolver().delete(getMetadataSyncUri(), null, null);
+        mContext.getContentResolver().delete(getMetadataSyncStateUri(), null, null);
+        requestMetadataSyncForAccount(syncAccount);
+    }
+
+    /**
+     * Turn on contact metadata sync for this {@param accountName} and turn off automatic sync
+     * for other accounts. If accountName is null, then turn off automatic sync for all accounts.
+     */
+    private void requestMetadataSyncForAccount(String accountName) {
+        for (Account account : getFocusGoogleAccounts()) {
+            if (!TextUtils.isEmpty(accountName) && accountName.equals(account.name)) {
+                ContentResolver.setIsSyncable(account, CONTACT_METADATA_AUTHORITY, 1 /*syncable*/);
+                ContentResolver.setSyncAutomatically(account, CONTACT_METADATA_AUTHORITY, true);
+
+                // Request sync.
+                final Bundle b = new Bundle();
+                b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+                ContentResolver.requestSync(account, CONTACT_METADATA_AUTHORITY, b);
+            } else {
+                // Turn off automatic sync for all other accounts.
+                ContentResolver.setSyncAutomatically(account, CONTACT_METADATA_AUTHORITY, false);
+            }
+        }
+    }
+
+    /**
+     * @return google accounts with "com.google" account type and null data set.
+     */
+    private List<Account> getFocusGoogleAccounts() {
+        List<Account> focusGoogleAccounts = new ArrayList<Account>();
+        final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(mContext);
+        List<AccountWithDataSet> accounts = accountTypeManager.getAccounts(true);
+        for (AccountWithDataSet account : accounts) {
+            if (GoogleAccountType.ACCOUNT_TYPE.equals(account.type) && account.dataSet == null) {
+                focusGoogleAccounts.add(account.getAccountOrNull());
+            }
+        }
+        return focusGoogleAccounts;
+    }
+
+    private static Uri getMetadataSyncUri() {
+        final Uri metadataUri = Uri.parse("content://" + CONTACT_METADATA_AUTHORITY);
+        return metadataUri.buildUpon()
+                .appendPath("metadata_sync")
+                .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
+                .build();
+    }
+
+    private static Uri getMetadataSyncStateUri() {
+        final Uri metadataUri = Uri.parse("content://" + CONTACT_METADATA_AUTHORITY);
+        return metadataUri.buildUpon()
+                .appendPath("metadata_sync_state")
+                .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
+                .build();
+    }
+
     public void registerChangeListener(ChangeListener listener) {
         if (mListener != null) unregisterChangeListener();
 
diff --git a/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java b/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java
index e143450..3a5c536 100644
--- a/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java
+++ b/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java
@@ -54,6 +54,11 @@
             preferenceScreen.removePreference((ListPreference) findPreference("accounts"));
         }
 
+        // STOPSHIP Show this option when 1) metadata sync is enabled and 2) at least one
+        // focus google account.
+        final PreferenceScreen preferenceScreen = getPreferenceScreen();
+        preferenceScreen.removePreference((ListPreference) findPreference("contactMetadata"));
+
         // Set build version of Contacts App.
         final PackageManager manager = getActivity().getPackageManager();
         try {