diff options
author | Cassie(Yitong) Wang <cassieyw@google.com> | 2019-05-17 15:59:19 -0700 |
---|---|---|
committer | Cassie(Yitong) Wang <cassieyw@google.com> | 2019-06-27 16:25:17 -0700 |
commit | df559d8486753af9927bc01fc68c058c03f1c0b2 (patch) | |
tree | cdd7f51af9e287fbc6ea3a020470b5311d1e4b01 | |
parent | 9128a19af948485ae75b4ca6d77529fc4f0a90da (diff) | |
download | Dialer-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.xml | 11 | ||||
-rw-r--r-- | res/values/strings.xml | 9 | ||||
-rw-r--r-- | res/xml/settings_page.xml | 6 | ||||
-rw-r--r-- | src/com/android/car/dialer/livedata/SharedPreferencesLiveData.java | 75 | ||||
-rw-r--r-- | src/com/android/car/dialer/ui/contact/ContactListViewModel.java | 103 | ||||
-rw-r--r-- | src/com/android/car/dialer/widget/WorkerExecutor.java | 59 |
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; + } +} |