summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCassie(Yitong) Wang <cassieyw@google.com>2019-05-17 15:59:19 -0700
committerCassie(Yitong) Wang <cassieyw@google.com>2019-06-27 16:25:17 -0700
commitdf559d8486753af9927bc01fc68c058c03f1c0b2 (patch)
treecdd7f51af9e287fbc6ea3a020470b5311d1e4b01
parent9128a19af948485ae75b4ca6d77529fc4f0a90da (diff)
downloadDialer-df559d8486753af9927bc01fc68c058c03f1c0b2.tar.gz
Add sort contact option in Dialer setting
So users can should whether to display contact list by first name or last name Bug: 132096685 Test: Manually Change-Id: Ieca42e2e352cb4f1ad028ee1c77d39741a42b084
-rw-r--r--res/values/arrays.xml11
-rw-r--r--res/values/strings.xml9
-rw-r--r--res/xml/settings_page.xml6
-rw-r--r--src/com/android/car/dialer/livedata/SharedPreferencesLiveData.java75
-rw-r--r--src/com/android/car/dialer/ui/contact/ContactListViewModel.java103
-rw-r--r--src/com/android/car/dialer/widget/WorkerExecutor.java59
6 files changed, 262 insertions, 1 deletions
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index bdb4aa94..9526d33d 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -36,4 +36,15 @@
<item>@string/contacts_title</item>
<item>@string/dialpad_title</item>
</string-array>
+
+ <string-array name="contact_order_entry_values">
+ <item>given_name</item>
+ <item>family_name</item>
+ </string-array>
+
+ <string-array name="contact_order_entries">
+ <!-- This array is mapped to contact_order_keys. -->
+ <item>@string/give_name_first_title</item>
+ <item>@string/family_name_first_title</item>
+ </string-array>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f98c6db4..685f9072 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -149,4 +149,13 @@
<!-- Title of the settings to change the start page [CHAR LIMIT=40]-->
<string name="pref_start_page_title">Set start page</string>
<string name="pref_start_page_key" translatable="false">set_start_page</string>
+
+ <!-- Title of the settings to sort contact order [CHAR LIMIT=40]-->
+ <string name="sort_order_title">Contact Order</string>
+ <string name="sort_order_key" translatable="false">contact_order</string>
+ <!-- Title of the settings for sorting Contacts in different orders -->
+ <!-- Title of given name [CHAR LIMIT=40]-->
+ <string name="give_name_first_title">First name</string>
+ <!-- Title of family name [CHAR LIMIT=40]-->
+ <string name="family_name_first_title">Last name</string>
</resources>
diff --git a/res/xml/settings_page.xml b/res/xml/settings_page.xml
index b8ffbc01..19876fef 100644
--- a/res/xml/settings_page.xml
+++ b/res/xml/settings_page.xml
@@ -24,4 +24,10 @@
android:title="@string/pref_start_page_title"
android:entries="@array/tabs_title"
android:entryValues="@array/tabs_config"/>
+
+ <ListPreference
+ android:key="@string/sort_order_key"
+ android:title="@string/sort_order_title"
+ android:entries="@array/contact_order_entries"
+ android:entryValues="@array/contact_order_entry_values"/>
</PreferenceScreen>
diff --git a/src/com/android/car/dialer/livedata/SharedPreferencesLiveData.java b/src/com/android/car/dialer/livedata/SharedPreferencesLiveData.java
new file mode 100644
index 00000000..13ae0493
--- /dev/null
+++ b/src/com/android/car/dialer/livedata/SharedPreferencesLiveData.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 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.car.dialer.livedata;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.text.TextUtils;
+
+import androidx.lifecycle.LiveData;
+import androidx.preference.PreferenceManager;
+
+import com.android.car.dialer.log.L;
+
+/**
+ * Provides SharedPreferences.
+ */
+public class SharedPreferencesLiveData extends LiveData<SharedPreferences> {
+ private static final String TAG = "CD.PreferenceLiveData";
+
+ private final SharedPreferences mSharedPreferences;
+ private final String mKey;
+
+ private final SharedPreferences.OnSharedPreferenceChangeListener
+ mOnSharedPreferenceChangeListener;
+
+ public SharedPreferencesLiveData(Context context, String key) {
+ mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ mKey = key;
+
+ mOnSharedPreferenceChangeListener = (sharedPreferences, k) -> {
+ if (TextUtils.equals(k, mKey)) {
+ updateSharedPreferences();
+ }
+ };
+ }
+
+ @Override
+ protected void onActive() {
+ updateSharedPreferences();
+ mSharedPreferences.registerOnSharedPreferenceChangeListener(
+ mOnSharedPreferenceChangeListener);
+ }
+
+ @Override
+ protected void onInactive() {
+ mSharedPreferences.unregisterOnSharedPreferenceChangeListener(
+ mOnSharedPreferenceChangeListener);
+ }
+
+ private void updateSharedPreferences() {
+ L.i(TAG, "Update SharedPreferences");
+ setValue(mSharedPreferences);
+ }
+
+ /**
+ * Returns the monitored shared preference key.
+ */
+ public String getKey() {
+ return mKey;
+ }
+}
diff --git a/src/com/android/car/dialer/ui/contact/ContactListViewModel.java b/src/com/android/car/dialer/ui/contact/ContactListViewModel.java
index 6cba4159..a0dbca86 100644
--- a/src/com/android/car/dialer/ui/contact/ContactListViewModel.java
+++ b/src/com/android/car/dialer/ui/contact/ContactListViewModel.java
@@ -17,28 +17,129 @@
package com.android.car.dialer.ui.contact;
import android.app.Application;
+import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
+import com.android.car.dialer.R;
+import com.android.car.dialer.livedata.SharedPreferencesLiveData;
+import com.android.car.dialer.widget.WorkerExecutor;
import com.android.car.telephony.common.Contact;
import com.android.car.telephony.common.InMemoryPhoneBook;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
/**
* View model for {@link ContactListFragment}.
*/
public class ContactListViewModel extends AndroidViewModel {
+
+ private final Context mContext;
+ private final LiveData<List<Contact>> mSortedContactListLiveData;
+
public ContactListViewModel(@NonNull Application application) {
super(application);
+ mContext = application.getApplicationContext();
+
+ String key = mContext.getString(R.string.sort_order_key);
+ SharedPreferencesLiveData preferencesLiveData =
+ new SharedPreferencesLiveData(mContext, key);
+ LiveData<List<Contact>> contactListLiveData = InMemoryPhoneBook.get().getContactsLiveData();
+ mSortedContactListLiveData = new SortedContactListLiveData(
+ mContext, contactListLiveData, preferencesLiveData);
}
/**
* Returns a live data which represents a list of all contacts.
*/
public LiveData<List<Contact>> getAllContacts() {
- return InMemoryPhoneBook.get().getContactsLiveData();
+ return mSortedContactListLiveData;
+ }
+
+ private static class SortedContactListLiveData extends MediatorLiveData<List<Contact>> {
+
+ private final LiveData<List<Contact>> mContactListLiveData;
+ private final SharedPreferencesLiveData mPreferencesLiveData;
+ private final Context mContext;
+
+ private Runnable mRunnable;
+
+ /**
+ * Sort by the default display order of a name. For western names it will be "Given Family".
+ * For unstructured names like east asian this will be the only order.
+ * Phone Dialer uses the same method for sorting given names.
+ *
+ * @see android.provider.ContactsContract.Contacts#DISPLAY_NAME_PRIMARY
+ */
+ private final Comparator<Contact> mFirstNameComparator =
+ (o1, o2) -> o1.compareByDisplayName(o2);
+
+ /**
+ * Sort by the alternative display order of a name. For western names it will be "Family,
+ * Given". For unstructured names like east asian this order will be ignored and treated as
+ * primary.
+ * Phone Dialer uses the same method for sorting family names.
+ *
+ * @see android.provider.ContactsContract.Contacts#DISPLAY_NAME_ALTERNATIVE
+ */
+ private final Comparator<Contact> mLastNameComparator =
+ (o1, o2) -> o1.compareByAltDisplayName(o2);
+
+ private SortedContactListLiveData(Context context,
+ @NonNull LiveData<List<Contact>> contactListLiveData,
+ @NonNull SharedPreferencesLiveData sharedPreferencesLiveData) {
+ mContext = context;
+ mContactListLiveData = contactListLiveData;
+ mPreferencesLiveData = sharedPreferencesLiveData;
+
+ addSource(mPreferencesLiveData, (trigger) -> updateSortedContactList());
+ addSource(mContactListLiveData, (trigger) -> updateSortedContactList());
+ }
+
+ private void updateSortedContactList() {
+ if (mContactListLiveData.getValue() == null) {
+ setValue(null);
+ return;
+ }
+
+ String key = mPreferencesLiveData.getKey();
+ String defaultValue = mContext.getResources().getStringArray(
+ R.array.contact_order_entry_values)[0];
+
+ List<Contact> contactList = mContactListLiveData.getValue();
+ Comparator<Contact> comparator;
+ if (mPreferencesLiveData.getValue() == null
+ || mPreferencesLiveData.getValue().getString(key, defaultValue)
+ .equals(defaultValue)) {
+ comparator = mFirstNameComparator;
+ } else {
+ comparator = mLastNameComparator;
+ }
+
+ // SingleThreadPoolExecutor is used here to avoid multiple threads sorting the list
+ // at the same time.
+ if (mRunnable != null) {
+ WorkerExecutor.getInstance().getSingleThreadPoolExecutor().remove(mRunnable);
+ }
+
+ mRunnable = () -> {
+ Collections.sort(contactList, comparator);
+ postValue(contactList);
+ };
+ WorkerExecutor.getInstance().getSingleThreadPoolExecutor().execute(mRunnable);
+ }
+
+ @Override
+ protected void onInactive() {
+ super.onInactive();
+ if (mRunnable != null) {
+ WorkerExecutor.getInstance().getSingleThreadPoolExecutor().remove(mRunnable);
+ }
+ }
}
}
diff --git a/src/com/android/car/dialer/widget/WorkerExecutor.java b/src/com/android/car/dialer/widget/WorkerExecutor.java
new file mode 100644
index 00000000..bfc92744
--- /dev/null
+++ b/src/com/android/car/dialer/widget/WorkerExecutor.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 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.car.dialer.widget;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * WorkerExecutor is a singleton tied to the application to provide
+ * ThreadPoolExecutor for Dialer.
+ */
+public class WorkerExecutor {
+ private static WorkerExecutor sWorkerExecutor;
+
+ private ThreadPoolExecutor mSingleThreadPoolExecutor;
+
+ /**
+ * Get the singleton WorkerExecutor for the application
+ */
+ public static WorkerExecutor getInstance() {
+ if (sWorkerExecutor == null) {
+ sWorkerExecutor = new WorkerExecutor();
+ }
+ return sWorkerExecutor;
+ }
+
+ private WorkerExecutor() {
+ mSingleThreadPoolExecutor = (ThreadPoolExecutor) Executors.newSingleThreadExecutor();
+ }
+
+ /**
+ * Returns the ThreadPoolExecutor.
+ */
+ public ThreadPoolExecutor getSingleThreadPoolExecutor() {
+ return mSingleThreadPoolExecutor;
+ }
+
+ /**
+ * Tears down the singleton WorkerExecutor for the application
+ */
+ public void tearDown() {
+ mSingleThreadPoolExecutor.shutdown();
+ sWorkerExecutor = null;
+ }
+}