From 5578d928bf777f721ec0ec04c84ddb7d3ea56edb Mon Sep 17 00:00:00 2001 From: twyen Date: Mon, 25 Jun 2018 12:36:26 -0700 Subject: Refactor ContactPreference Moving it outside contacts.commons reduces dependency to legacy code. Also removed redundant implementations such as caching SharedPreferences (it is already cached), custom ListPreferences (standard ListPreferences already have what we want), and corrected preference storage location (allow standard ListPreferences to work) TEST=TAP Test: TAP PiperOrigin-RevId: 202000393 Change-Id: I45374e610b3510784b5a4da92e5d8462cbfc92bb --- .../android/dialer/contacts/ContactsComponent.java | 39 +++++ .../android/dialer/contacts/ContactsModule.java | 32 ++++ .../contacts/displaypreference/AndroidManifest.xml | 18 +++ .../ContactDisplayPreferences.java | 162 +++++++++++++++++++++ .../ContactDisplayPreferencesImpl.java | 115 +++++++++++++++ .../res/values/display_preference.xml | 37 +++++ .../displaypreference/res/values/strings.xml | 48 ++++++ 7 files changed, 451 insertions(+) create mode 100644 java/com/android/dialer/contacts/ContactsComponent.java create mode 100644 java/com/android/dialer/contacts/ContactsModule.java create mode 100644 java/com/android/dialer/contacts/displaypreference/AndroidManifest.xml create mode 100644 java/com/android/dialer/contacts/displaypreference/ContactDisplayPreferences.java create mode 100644 java/com/android/dialer/contacts/displaypreference/ContactDisplayPreferencesImpl.java create mode 100644 java/com/android/dialer/contacts/displaypreference/res/values/display_preference.xml create mode 100644 java/com/android/dialer/contacts/displaypreference/res/values/strings.xml (limited to 'java/com/android/dialer/contacts') diff --git a/java/com/android/dialer/contacts/ContactsComponent.java b/java/com/android/dialer/contacts/ContactsComponent.java new file mode 100644 index 000000000..5c4097ace --- /dev/null +++ b/java/com/android/dialer/contacts/ContactsComponent.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 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.dialer.contacts; + +import android.content.Context; +import com.android.dialer.contacts.displaypreference.ContactDisplayPreferences; +import com.android.dialer.inject.HasRootComponent; +import dagger.Subcomponent; + +/** Component for contacts related utilities */ +@Subcomponent +public abstract class ContactsComponent { + + public abstract ContactDisplayPreferences contactDisplayPreferences(); + + public static ContactsComponent get(Context context) { + return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) + .contactsComponent(); + } + + /** Used to refer to the root application component. */ + public interface HasComponent { + ContactsComponent contactsComponent(); + } +} diff --git a/java/com/android/dialer/contacts/ContactsModule.java b/java/com/android/dialer/contacts/ContactsModule.java new file mode 100644 index 000000000..979c525eb --- /dev/null +++ b/java/com/android/dialer/contacts/ContactsModule.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 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.dialer.contacts; + +import com.android.dialer.contacts.displaypreference.ContactDisplayPreferences; +import com.android.dialer.contacts.displaypreference.ContactDisplayPreferencesImpl; +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.InstallIn; +import dagger.Binds; +import dagger.Module; + +/** Module for standard {@link ContactsComponent} */ +@InstallIn(variants = {DialerVariant.DIALER_TEST}) +@Module +public abstract class ContactsModule { + @Binds + public abstract ContactDisplayPreferences to(ContactDisplayPreferencesImpl impl); +} diff --git a/java/com/android/dialer/contacts/displaypreference/AndroidManifest.xml b/java/com/android/dialer/contacts/displaypreference/AndroidManifest.xml new file mode 100644 index 000000000..27514615e --- /dev/null +++ b/java/com/android/dialer/contacts/displaypreference/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + \ No newline at end of file diff --git a/java/com/android/dialer/contacts/displaypreference/ContactDisplayPreferences.java b/java/com/android/dialer/contacts/displaypreference/ContactDisplayPreferences.java new file mode 100644 index 000000000..dca466ebf --- /dev/null +++ b/java/com/android/dialer/contacts/displaypreference/ContactDisplayPreferences.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2018 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.dialer.contacts.displaypreference; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.text.TextUtils; +import java.util.Arrays; + +/** Handles name ordering of a contact (Given name first or family name first.) */ +public interface ContactDisplayPreferences { + + /** + * A enum whose value is a String from a Android string resource which can only be resolved at run + * time. + */ + interface StringResEnum { + + @StringRes + int getStringRes(); + + default String getValue(Context context) { + return context.getString(getStringRes()); + } + + static & StringResEnum> T fromValue( + Context context, T[] values, String value) { + return Arrays.stream(values) + .filter(enumValue -> TextUtils.equals(enumValue.getValue(context), value)) + // MoreCollectors.onlyElement() is not available to android guava. + .reduce( + (a, b) -> { + throw new AssertionError("multiple result"); + }) + .get(); + } + } + + /** Order when displaying the name; */ + enum DisplayOrder implements StringResEnum { + + /** + * 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. + * + * @see android.provider.ContactsContract.Contacts#DISPLAY_NAME_PRIMARY + */ + PRIMARY(R.string.display_options_view_given_name_first_value), + /** + * 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. + * + * @see android.provider.ContactsContract.Contacts#DISPLAY_NAME_ALTERNATIVE + */ + ALTERNATIVE(R.string.display_options_view_family_name_first_value); + + @StringRes private final int value; + + DisplayOrder(@StringRes int value) { + this.value = value; + } + + @Override + @StringRes + public int getStringRes() { + return value; + } + + static DisplayOrder fromValue(Context context, String value) { + return StringResEnum.fromValue(context, DisplayOrder.values(), value); + } + } + + /** + * Order when sorting the name. In some conventions, names are displayed as given name first, but + * sorted by family name. + */ + enum SortOrder implements StringResEnum { + /** + * 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. + * + * @see android.provider.ContactsContract.Contacts#DISPLAY_NAME_PRIMARY + */ + BY_PRIMARY(R.string.display_options_sort_by_given_name_value), + /** + * 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. + * + * @see android.provider.ContactsContract.Contacts#DISPLAY_NAME_ALTERNATIVE + */ + BY_ALTERNATIVE(R.string.display_options_sort_by_family_name_value); + + @StringRes private final int value; + + SortOrder(@StringRes int value) { + this.value = value; + } + + @Override + @StringRes + public int getStringRes() { + return value; + } + + static SortOrder fromValue(Context context, String value) { + return StringResEnum.fromValue(context, SortOrder.values(), value); + } + } + + DisplayOrder getDisplayOrder(); + + void setDisplayOrder(DisplayOrder displayOrder); + + SortOrder getSortOrder(); + + void setSortOrder(SortOrder sortOrder); + + /** Selects display name based on {@link DisplayOrder} */ + default String getDisplayName(@Nullable String primaryName, @Nullable String alternativeName) { + if (TextUtils.isEmpty(alternativeName)) { + return primaryName; + } + switch (getDisplayOrder()) { + case PRIMARY: + return primaryName; + case ALTERNATIVE: + return alternativeName; + } + throw new AssertionError("exhaustive switch"); + } + + /** Selects sort name based on {@link SortOrder} */ + default String getSortName(@Nullable String primaryName, @Nullable String alternativeName) { + if (TextUtils.isEmpty(alternativeName)) { + return primaryName; + } + switch (getSortOrder()) { + case BY_PRIMARY: + return primaryName; + case BY_ALTERNATIVE: + return alternativeName; + } + throw new AssertionError("exhaustive switch"); + } +} diff --git a/java/com/android/dialer/contacts/displaypreference/ContactDisplayPreferencesImpl.java b/java/com/android/dialer/contacts/displaypreference/ContactDisplayPreferencesImpl.java new file mode 100644 index 000000000..6072cc12c --- /dev/null +++ b/java/com/android/dialer/contacts/displaypreference/ContactDisplayPreferencesImpl.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2018 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.dialer.contacts.displaypreference; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.UserManager; +import android.preference.PreferenceManager; +import com.android.dialer.inject.ApplicationContext; +import javax.inject.Inject; + +/** Implementation of {@link ContactDisplayPreferences} backed by a {@link SharedPreferences} */ +public final class ContactDisplayPreferencesImpl implements ContactDisplayPreferences { + + private final Context appContext; + private final SharedPreferences sharedPreferences; + private final String displayOrderKey; + private final String sortOrderKey; + + @Inject + ContactDisplayPreferencesImpl(@ApplicationContext Context appContext) { + this.appContext = appContext; + // @Unencrypted preference would be a better choice, but Android Preference only supports the + // default file. Names cannot be shown on @Unencrypted anyway. + this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext); + displayOrderKey = appContext.getString(R.string.display_options_view_names_as_key); + sortOrderKey = appContext.getString(R.string.display_options_sort_list_by_key); + } + + @Override + public DisplayOrder getDisplayOrder() { + migrate(); + if (!sharedPreferences.contains(displayOrderKey)) { + return DisplayOrder.PRIMARY; + } + return DisplayOrder.fromValue(appContext, sharedPreferences.getString(displayOrderKey, null)); + } + + @Override + public void setDisplayOrder(DisplayOrder displayOrder) { + sharedPreferences.edit().putString(displayOrderKey, displayOrder.getValue(appContext)).apply(); + } + + @Override + public SortOrder getSortOrder() { + migrate(); + if (!sharedPreferences.contains(sortOrderKey)) { + return SortOrder.BY_PRIMARY; + } + return SortOrder.fromValue(appContext, sharedPreferences.getString(sortOrderKey, null)); + } + + @Override + public void setSortOrder(SortOrder sortOrder) { + sharedPreferences.edit().putString(sortOrderKey, sortOrder.getValue(appContext)).apply(); + } + + /** + * Moves the stored values to the standard location. + * + *

Usually preferences are stored in {@code package.name_preferences.xml}. However the old + * com.android.contacts.common.preference.ContactsPreferences stored it in {@code + * package.name.xml} which is incompatible with the regular {@link android.preference.Preference} + * widgets. + */ + private void migrate() { + if (!appContext.getSystemService(UserManager.class).isUserUnlocked()) { + return; + } + SharedPreferences oldPreference = + appContext.getSharedPreferences(appContext.getPackageName(), Context.MODE_PRIVATE); + if (oldPreference.contains(displayOrderKey) || oldPreference.contains(sortOrderKey)) { + sharedPreferences + .edit() + .putString( + displayOrderKey, + translateLegacyDisplayOrder(oldPreference.getInt(displayOrderKey, 1))) + .putString(sortOrderKey, translateLegacySortOrder(oldPreference.getInt(sortOrderKey, 1))) + .apply(); + oldPreference.edit().remove(displayOrderKey).remove(sortOrderKey).apply(); + } + } + + private String translateLegacyDisplayOrder(int legacyValue) { + switch (legacyValue) { + case 2: + return DisplayOrder.ALTERNATIVE.getValue(appContext); + default: + return DisplayOrder.PRIMARY.getValue(appContext); + } + } + + private String translateLegacySortOrder(int legacyValue) { + switch (legacyValue) { + case 2: + return SortOrder.BY_ALTERNATIVE.getValue(appContext); + default: + return SortOrder.BY_PRIMARY.getValue(appContext); + } + } +} diff --git a/java/com/android/dialer/contacts/displaypreference/res/values/display_preference.xml b/java/com/android/dialer/contacts/displaypreference/res/values/display_preference.xml new file mode 100644 index 000000000..b74b8b999 --- /dev/null +++ b/java/com/android/dialer/contacts/displaypreference/res/values/display_preference.xml @@ -0,0 +1,37 @@ + + + + + @string/display_options_view_given_name_first + @string/display_options_view_family_name_first + + + + @string/display_options_view_given_name_first_value + @string/display_options_view_family_name_first_value + + + + @string/display_options_sort_by_given_name + @string/display_options_sort_by_family_name + + + + @string/display_options_sort_by_given_name_value + @string/display_options_sort_by_family_name_value + + \ No newline at end of file diff --git a/java/com/android/dialer/contacts/displaypreference/res/values/strings.xml b/java/com/android/dialer/contacts/displaypreference/res/values/strings.xml new file mode 100644 index 000000000..8914ddfa4 --- /dev/null +++ b/java/com/android/dialer/contacts/displaypreference/res/values/strings.xml @@ -0,0 +1,48 @@ + + + + + + + Sort by + + android.contacts.SORT_ORDER + + + First name + + sort_by_given_name + + + Last name + + sort_by_family_name + + + Name format + android.contacts.DISPLAY_ORDER + + + First name first + + view_given_name_first + + + Last name first + + view_family_name_first + -- cgit v1.2.3