Make ContactPreferences use SharedPreferences instead of System settings (3/5)

* Move constants that originally lived in the framework into ContactsCommon
* Use SharedPreferences instead of System settings to persist preferences
* Use a SharedPreferenceListener to monitor changes instead of a content observer
on system settings
* Move DisplayOrderPreference and SortOrderPreference into ContactsCommon so that
it can be used by Dialer
* Create base DialerSettingsActivity in Dialer, and make GoogleDialerSettingsActivity
extend it
Bug: 16153186

Change-Id: Ib3500b82b03960a30b565f1024f20f78879d3ce3
diff --git a/src/com/android/contacts/common/list/ContactListAdapter.java b/src/com/android/contacts/common/list/ContactListAdapter.java
index e158238..5826025 100644
--- a/src/com/android/contacts/common/list/ContactListAdapter.java
+++ b/src/com/android/contacts/common/list/ContactListAdapter.java
@@ -31,6 +31,7 @@
 import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.R;
+import com.android.contacts.common.preference.ContactsPreferences;
 
 /**
  * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
@@ -360,13 +361,13 @@
     protected final String[] getProjection(boolean forSearch) {
         final int sortOrder = getContactNameDisplayOrder();
         if (forSearch) {
-            if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+            if (sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
                 return ContactQuery.FILTER_PROJECTION_PRIMARY;
             } else {
                 return ContactQuery.FILTER_PROJECTION_ALTERNATIVE;
             }
         } else {
-            if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+            if (sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
                 return ContactQuery.CONTACT_PROJECTION_PRIMARY;
             } else {
                 return ContactQuery.CONTACT_PROJECTION_ALTERNATIVE;
diff --git a/src/com/android/contacts/common/list/DefaultContactListAdapter.java b/src/com/android/contacts/common/list/DefaultContactListAdapter.java
index fba285c..b47608e 100644
--- a/src/com/android/contacts/common/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/common/list/DefaultContactListAdapter.java
@@ -86,7 +86,7 @@
         }
 
         String sortOrder;
-        if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+        if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
             sortOrder = Contacts.SORT_KEY_PRIMARY;
         } else {
             sortOrder = Contacts.SORT_KEY_ALTERNATIVE;
diff --git a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
index 135ea58..cde25da 100644
--- a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
@@ -39,6 +39,7 @@
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.extensions.ExtendedPhoneDirectoriesManager;
 import com.android.contacts.common.extensions.ExtensionsFactory;
+import com.android.contacts.common.preference.ContactsPreferences;
 import com.android.contacts.common.util.Constants;
 
 import java.util.ArrayList;
@@ -181,14 +182,13 @@
             loader.setUri(builder.build());
 
             // TODO a projection that includes the search snippet
-            if (getContactNameDisplayOrder() ==
-                    ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+            if (getContactNameDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY) {
                 loader.setProjection(PhoneQuery.PROJECTION_PRIMARY);
             } else {
                 loader.setProjection(PhoneQuery.PROJECTION_ALTERNATIVE);
             }
 
-            if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+            if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
                 loader.setSortOrder(Phone.SORT_KEY_PRIMARY);
             } else {
                 loader.setSortOrder(Phone.SORT_KEY_ALTERNATIVE);
diff --git a/src/com/android/contacts/common/preference/ContactsPreferences.java b/src/com/android/contacts/common/preference/ContactsPreferences.java
index 56390fd..311d007 100644
--- a/src/com/android/contacts/common/preference/ContactsPreferences.java
+++ b/src/com/android/contacts/common/preference/ContactsPreferences.java
@@ -18,6 +18,9 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.database.ContentObserver;
 import android.os.Handler;
 import android.provider.ContactsContract;
@@ -29,21 +32,48 @@
 /**
  * Manages user preferences for contacts.
  */
-public final class ContactsPreferences extends ContentObserver {
+public final class ContactsPreferences implements OnSharedPreferenceChangeListener {
+
+    /**
+     * The value for the DISPLAY_ORDER key to show the given name first.
+     */
+    public static final int DISPLAY_ORDER_PRIMARY = 1;
+
+    /**
+     * The value for the DISPLAY_ORDER key to show the family name first.
+     */
+    public static final int DISPLAY_ORDER_ALTERNATIVE = 2;
+
+    public static final String DISPLAY_ORDER_KEY = "android.contacts.DISPLAY_ORDER";
+
+    /**
+     * The value for the SORT_ORDER key corresponding to sort by given name first.
+     */
+    public static final int SORT_ORDER_PRIMARY = 1;
+
+    public static final String SORT_ORDER_KEY = "android.contacts.SORT_ORDER";
+
+    /**
+     * The value for the SORT_ORDER key corresponding to sort by family name first.
+     */
+    public static final int SORT_ORDER_ALTERNATIVE = 2;
 
     public static final String PREF_DISPLAY_ONLY_PHONES = "only_phones";
     public static final boolean PREF_DISPLAY_ONLY_PHONES_DEFAULT = false;
 
-    private Context mContext;
+    private final Context mContext;
     private int mSortOrder = -1;
     private int mDisplayOrder = -1;
     private ChangeListener mListener = null;
     private Handler mHandler;
+    private final SharedPreferences mPreferences;
 
     public ContactsPreferences(Context context) {
-        super(null);
         mContext = context;
         mHandler = new Handler();
+        mPreferences = mContext.getSharedPreferences(context.getPackageName(),
+                Context.MODE_PRIVATE);
+        maybeMigrateSystemSettings();
     }
 
     public boolean isSortOrderUserChangeable() {
@@ -52,9 +82,9 @@
 
     public int getDefaultSortOrder() {
         if (mContext.getResources().getBoolean(R.bool.config_default_sort_order_primary)) {
-            return ContactsContract.Preferences.SORT_ORDER_PRIMARY;
+            return SORT_ORDER_PRIMARY;
         } else {
-            return ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE;
+            return SORT_ORDER_ALTERNATIVE;
         }
     }
 
@@ -62,22 +92,17 @@
         if (!isSortOrderUserChangeable()) {
             return getDefaultSortOrder();
         }
-
         if (mSortOrder == -1) {
-            try {
-                mSortOrder = Settings.System.getInt(mContext.getContentResolver(),
-                        ContactsContract.Preferences.SORT_ORDER);
-            } catch (SettingNotFoundException e) {
-                mSortOrder = getDefaultSortOrder();
-            }
+            mSortOrder = mPreferences.getInt(SORT_ORDER_KEY, getDefaultSortOrder());
         }
         return mSortOrder;
     }
 
     public void setSortOrder(int sortOrder) {
         mSortOrder = sortOrder;
-        Settings.System.putInt(mContext.getContentResolver(),
-                ContactsContract.Preferences.SORT_ORDER, sortOrder);
+        final Editor editor = mPreferences.edit();
+        editor.putInt(SORT_ORDER_KEY, sortOrder);
+        editor.commit();
     }
 
     public boolean isDisplayOrderUserChangeable() {
@@ -86,9 +111,9 @@
 
     public int getDefaultDisplayOrder() {
         if (mContext.getResources().getBoolean(R.bool.config_default_display_order_primary)) {
-            return ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY;
+            return DISPLAY_ORDER_PRIMARY;
         } else {
-            return ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE;
+            return DISPLAY_ORDER_ALTERNATIVE;
         }
     }
 
@@ -96,22 +121,17 @@
         if (!isDisplayOrderUserChangeable()) {
             return getDefaultDisplayOrder();
         }
-
         if (mDisplayOrder == -1) {
-            try {
-                mDisplayOrder = Settings.System.getInt(mContext.getContentResolver(),
-                        ContactsContract.Preferences.DISPLAY_ORDER);
-            } catch (SettingNotFoundException e) {
-                mDisplayOrder = getDefaultDisplayOrder();
-            }
+            mDisplayOrder = mPreferences.getInt(DISPLAY_ORDER_KEY, getDefaultDisplayOrder());
         }
         return mDisplayOrder;
     }
 
     public void setDisplayOrder(int displayOrder) {
         mDisplayOrder = displayOrder;
-        Settings.System.putInt(mContext.getContentResolver(),
-                ContactsContract.Preferences.DISPLAY_ORDER, displayOrder);
+        final Editor editor = mPreferences.edit();
+        editor.putInt(DISPLAY_ORDER_KEY, displayOrder);
+        editor.commit();
     }
 
     public void registerChangeListener(ChangeListener listener) {
@@ -120,35 +140,33 @@
         mListener = listener;
 
         // Reset preferences to "unknown" because they may have changed while the
-        // observer was unregistered.
+        // listener was unregistered.
         mDisplayOrder = -1;
         mSortOrder = -1;
 
-        final ContentResolver contentResolver = mContext.getContentResolver();
-        contentResolver.registerContentObserver(
-                Settings.System.getUriFor(
-                        ContactsContract.Preferences.SORT_ORDER), false, this);
-        contentResolver.registerContentObserver(
-                Settings.System.getUriFor(
-                        ContactsContract.Preferences.DISPLAY_ORDER), false, this);
+        mPreferences.registerOnSharedPreferenceChangeListener(this);
     }
 
     public void unregisterChangeListener() {
         if (mListener != null) {
-            mContext.getContentResolver().unregisterContentObserver(this);
             mListener = null;
         }
+
+        mPreferences.unregisterOnSharedPreferenceChangeListener(this);
     }
 
     @Override
-    public void onChange(boolean selfChange) {
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
         // This notification is not sent on the Ui thread. Use the previously created Handler
         // to switch to the Ui thread
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                mSortOrder = -1;
-                mDisplayOrder = -1;
+                if (DISPLAY_ORDER_KEY.equals(key)) {
+                    mDisplayOrder = getDisplayOrder();
+                } else if (SORT_ORDER_KEY.equals(key)) {
+                    mSortOrder = getSortOrder();
+                }
                 if (mListener != null) mListener.onChange();
             }
         });
@@ -157,4 +175,31 @@
     public interface ChangeListener {
         void onChange();
     }
+
+    /**
+     * If there are currently no preferences (which means this is the first time we are run),
+     * check to see if there are any preferences stored in system settings (pre-L) which can be
+     * copied into our own SharedPreferences.
+     */
+    private void maybeMigrateSystemSettings() {
+        if (!mPreferences.contains(SORT_ORDER_KEY)) {
+            int sortOrder = getDefaultSortOrder();
+            try {
+                 sortOrder = Settings.System.getInt(mContext.getContentResolver(),
+                        SORT_ORDER_KEY);
+            } catch (SettingNotFoundException e) {
+            }
+            setSortOrder(sortOrder);
+        }
+
+        if (!mPreferences.contains(DISPLAY_ORDER_KEY)) {
+            int displayOrder = getDefaultDisplayOrder();
+            try {
+                displayOrder = Settings.System.getInt(mContext.getContentResolver(),
+                        DISPLAY_ORDER_KEY);
+            } catch (SettingNotFoundException e) {
+            }
+            setDisplayOrder(displayOrder);
+        }
+    }
 }
diff --git a/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java b/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java
new file mode 100644
index 0000000..e49f38a
--- /dev/null
+++ b/src/com/android/contacts/common/preference/DisplayOptionsPreferenceFragment.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 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.os.Bundle;
+import android.preference.PreferenceFragment;
+
+import com.android.contacts.common.R;
+
+/**
+ * This fragment shows the preferences for the first header.
+ */
+public class DisplayOptionsPreferenceFragment extends PreferenceFragment {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.preference_display_options);
+    }
+}
+
diff --git a/src/com/android/contacts/common/preference/DisplayOrderPreference.java b/src/com/android/contacts/common/preference/DisplayOrderPreference.java
new file mode 100644
index 0000000..6a182c5
--- /dev/null
+++ b/src/com/android/contacts/common/preference/DisplayOrderPreference.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2010 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.Builder;
+import android.content.Context;
+import android.preference.ListPreference;
+import android.provider.ContactsContract;
+import android.util.AttributeSet;
+
+import com.android.contacts.common.R;
+import com.android.contacts.common.preference.ContactsPreferences;
+
+/**
+ * Custom preference: view-name-as (first name first or last name first).
+ */
+public final class DisplayOrderPreference extends ListPreference {
+
+    private ContactsPreferences mPreferences;
+    private Context mContext;
+
+    public DisplayOrderPreference(Context context) {
+        super(context);
+        prepare();
+    }
+
+    public DisplayOrderPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        prepare();
+    }
+
+    private void prepare() {
+        mContext = getContext();
+        mPreferences = new ContactsPreferences(mContext);
+        setEntries(new String[]{
+                mContext.getString(R.string.display_options_view_given_name_first),
+                mContext.getString(R.string.display_options_view_family_name_first),
+        });
+        setEntryValues(new String[]{
+                String.valueOf(ContactsPreferences.DISPLAY_ORDER_PRIMARY),
+                String.valueOf(ContactsPreferences.DISPLAY_ORDER_ALTERNATIVE),
+        });
+        setValue(String.valueOf(mPreferences.getDisplayOrder()));
+    }
+
+    @Override
+    protected boolean shouldPersist() {
+        return false;   // This preference takes care of its own storage
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        switch (mPreferences.getDisplayOrder()) {
+            case ContactsPreferences.DISPLAY_ORDER_PRIMARY:
+                return mContext.getString(R.string.display_options_view_given_name_first);
+            case ContactsPreferences.DISPLAY_ORDER_ALTERNATIVE:
+                return mContext.getString(R.string.display_options_view_family_name_first);
+        }
+        return null;
+    }
+
+    @Override
+    protected boolean persistString(String value) {
+        int newValue = Integer.parseInt(value);
+        if (newValue != mPreferences.getDisplayOrder()) {
+            mPreferences.setDisplayOrder(newValue);
+            notifyChanged();
+        }
+        return true;
+    }
+
+    @Override
+    // UX recommendation is not to show cancel button on such lists.
+    protected void onPrepareDialogBuilder(Builder builder) {
+        super.onPrepareDialogBuilder(builder);
+        builder.setNegativeButton(null, null);
+    }
+}
diff --git a/src/com/android/contacts/common/preference/SortOrderPreference.java b/src/com/android/contacts/common/preference/SortOrderPreference.java
new file mode 100644
index 0000000..dfd9550
--- /dev/null
+++ b/src/com/android/contacts/common/preference/SortOrderPreference.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2010 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.Builder;
+import android.content.Context;
+import android.preference.ListPreference;
+import android.util.AttributeSet;
+
+import com.android.contacts.common.R;
+import com.android.contacts.common.preference.ContactsPreferences;
+
+/**
+ * Custom preference: sort-by.
+ */
+public final class SortOrderPreference extends ListPreference {
+
+    private ContactsPreferences mPreferences;
+    private Context mContext;
+
+    public SortOrderPreference(Context context) {
+        super(context);
+        prepare();
+    }
+
+    public SortOrderPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        prepare();
+    }
+
+    private void prepare() {
+        mContext = getContext();
+        mPreferences = new ContactsPreferences(mContext);
+        setEntries(new String[]{
+                mContext.getString(R.string.display_options_sort_by_given_name),
+                mContext.getString(R.string.display_options_sort_by_family_name),
+        });
+        setEntryValues(new String[]{
+                String.valueOf(ContactsPreferences.SORT_ORDER_PRIMARY),
+                String.valueOf(ContactsPreferences.SORT_ORDER_ALTERNATIVE),
+        });
+        setValue(String.valueOf(mPreferences.getSortOrder()));
+    }
+
+    @Override
+    protected boolean shouldPersist() {
+        return false;   // This preference takes care of its own storage
+    }
+
+    @Override
+    public CharSequence getSummary() {
+        switch (mPreferences.getSortOrder()) {
+            case ContactsPreferences.SORT_ORDER_PRIMARY:
+                return mContext.getString(R.string.display_options_sort_by_given_name);
+            case ContactsPreferences.SORT_ORDER_ALTERNATIVE:
+                return mContext.getString(R.string.display_options_sort_by_family_name);
+        }
+        return null;
+    }
+
+    @Override
+    protected boolean persistString(String value) {
+        int newValue = Integer.parseInt(value);
+        if (newValue != mPreferences.getSortOrder()) {
+            mPreferences.setSortOrder(newValue);
+            notifyChanged();
+        }
+        return true;
+    }
+
+    @Override
+    // UX recommendation is not to show cancel button on such lists.
+    protected void onPrepareDialogBuilder(Builder builder) {
+        super.onPrepareDialogBuilder(builder);
+        builder.setNegativeButton(null, null);
+    }
+}
diff --git a/tests/src/com/android/contacts/common/list/ContactListItemViewTest.java b/tests/src/com/android/contacts/common/list/ContactListItemViewTest.java
index 9c69f24..222863c 100644
--- a/tests/src/com/android/contacts/common/list/ContactListItemViewTest.java
+++ b/tests/src/com/android/contacts/common/list/ContactListItemViewTest.java
@@ -26,6 +26,7 @@
 
 import com.android.contacts.common.format.SpannedTestUtils;
 import com.android.contacts.common.list.ContactListItemView;
+import com.android.contacts.common.preference.ContactsPreferences;
 
 /**
  * Unit tests for {@link com.android.contacts.common.list.ContactListItemView}.
@@ -56,7 +57,7 @@
         Cursor cursor = createCursor("John Doe", "Doe John");
         ContactListItemView view = createView();
 
-        view.showDisplayName(cursor, 0, ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY);
+        view.showDisplayName(cursor, 0, ContactsPreferences.DISPLAY_ORDER_PRIMARY);
 
         assertEquals(view.getNameTextView().getText().toString(), "John Doe");
     }
@@ -66,7 +67,7 @@
         ContactListItemView view = createView();
 
         view.setUnknownNameText("unknown");
-        view.showDisplayName(cursor, 0, ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY);
+        view.showDisplayName(cursor, 0, ContactsPreferences.DISPLAY_ORDER_PRIMARY);
 
         assertEquals(view.getNameTextView().getText().toString(), "unknown");
     }
@@ -76,7 +77,7 @@
         ContactListItemView view = createView();
 
         view.setHighlightedPrefix("DOE");
-        view.showDisplayName(cursor, 0, ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY);
+        view.showDisplayName(cursor, 0, ContactsPreferences.DISPLAY_ORDER_PRIMARY);
 
         CharSequence seq = view.getNameTextView().getText();
         assertEquals("John Doe", seq.toString());
@@ -88,7 +89,7 @@
         ContactListItemView view = createView();
 
         view.setHighlightedPrefix("DOE");
-        view.showDisplayName(cursor, 0, ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE);
+        view.showDisplayName(cursor, 0, ContactsPreferences.DISPLAY_ORDER_ALTERNATIVE);
 
         CharSequence seq = view.getNameTextView().getText();
         assertEquals("John Doe", seq.toString());