1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.settings.language;
18 
19 import android.content.Context;
20 
21 import androidx.annotation.Nullable;
22 import androidx.annotation.VisibleForTesting;
23 import androidx.preference.Preference;
24 import androidx.preference.PreferenceCategory;
25 import androidx.preference.PreferenceGroup;
26 
27 import com.android.car.settings.R;
28 import com.android.car.settings.common.Logger;
29 import com.android.car.settings.common.PreferenceUtil;
30 import com.android.car.ui.preference.CarUiPreference;
31 import com.android.internal.app.LocaleHelper;
32 import com.android.internal.app.LocaleStore;
33 import com.android.internal.app.SuggestedLocaleAdapter;
34 
35 import java.util.Locale;
36 import java.util.Set;
37 
38 /**
39  * Provides a wrapper around the {@link SuggestedLocaleAdapter} to create Preferences to populate
40  * the Language Settings screen.
41  */
42 public class LocalePreferenceProvider {
43 
44     private static final Logger LOG = new Logger(LanguagePickerPreferenceController.class);
45 
46     /** Creates a new instance of the preference provider. */
newInstance(Context context, Set<LocaleStore.LocaleInfo> localeInfoSet, @Nullable LocaleStore.LocaleInfo parentLocale)47     public static LocalePreferenceProvider newInstance(Context context,
48             Set<LocaleStore.LocaleInfo> localeInfoSet,
49             @Nullable LocaleStore.LocaleInfo parentLocale) {
50         SuggestedLocaleAdapter adapter = createSuggestedLocaleAdapter(context, localeInfoSet,
51                 parentLocale);
52         return new LocalePreferenceProvider(context, adapter);
53     }
54 
55     /**
56      * Header types are copied from {@link SuggestedLocaleAdapter} in order to be able to
57      * determine the header rows.
58      */
59     @VisibleForTesting
60     static final int TYPE_HEADER_SUGGESTED = 0;
61     @VisibleForTesting
62     static final int TYPE_HEADER_ALL_OTHERS = 1;
63     @VisibleForTesting
64     static final int TYPE_LOCALE = 2;
65 
66     private final Context mContext;
67     private SuggestedLocaleAdapter mSuggestedLocaleAdapter;
68 
69     @VisibleForTesting
LocalePreferenceProvider(Context context, SuggestedLocaleAdapter localeAdapter)70     LocalePreferenceProvider(Context context, SuggestedLocaleAdapter localeAdapter) {
71         mContext = context;
72         mSuggestedLocaleAdapter = localeAdapter;
73     }
74 
75     /**
76      * Populates the base preference group based on the hierarchy provided by this provider.
77      *
78      * @param base     the preference container which will hold the language preferences created by
79      *                 this provider
80      * @param listener the click listener registered to the language/locale preferences contained in
81      *                 the base preference group
82      */
populateBasePreference(PreferenceGroup base, Set<String> ignorables, Preference.OnPreferenceClickListener listener)83     public void populateBasePreference(PreferenceGroup base, Set<String> ignorables,
84             Preference.OnPreferenceClickListener listener) {
85         /*
86          * LocalePreferenceProvider can give elements to be represented in 2 ways. In the first
87          * way, it simply provides the LocalePreferences which lists the available options. In the
88          * second way, this provider may also provide PreferenceCategories to break up the
89          * options into "Suggested" and "All others". The screen is constructed by taking a look
90          * at the type of Preference that is provided through LocalePreferenceProvider.
91          *
92          * In the first case (no subcategories), preferences are added directly to the base
93          * container. Otherwise, elements are added to the last category that was provided
94          * (stored in "category").
95          */
96         PreferenceCategory category = null;
97         for (int position = 0; position < mSuggestedLocaleAdapter.getCount(); position++) {
98             Preference preference = getPreference(position, ignorables);
99             if (PreferenceUtil.checkPreferenceType(preference, PreferenceCategory.class)) {
100                 category = (PreferenceCategory) preference;
101                 base.addPreference(category);
102             } else {
103                 preference.setOnPreferenceClickListener(listener);
104                 if (category == null) {
105                     base.addPreference(preference);
106                 } else {
107                     category.addPreference(preference);
108                 }
109             }
110         }
111     }
112 
113     /**
114      * Constructs a PreferenceCategory or Preference with locale arguments based on the type of item
115      * provided.
116      */
getPreference(int position, Set<String> ignorables)117     private Preference getPreference(int position, Set<String> ignorables) {
118         int type = mSuggestedLocaleAdapter.getItemViewType(position);
119         switch (type) {
120             case TYPE_HEADER_SUGGESTED:
121             case TYPE_HEADER_ALL_OTHERS:
122                 PreferenceCategory category = new PreferenceCategory(mContext);
123                 category.setTitle(type == TYPE_HEADER_SUGGESTED
124                         ? R.string.language_picker_list_suggested_header
125                         : R.string.language_picker_list_all_header);
126                 return category;
127             case TYPE_LOCALE:
128                 LocaleStore.LocaleInfo info =
129                         (LocaleStore.LocaleInfo) mSuggestedLocaleAdapter.getItem(position);
130                 CarUiPreference preference = new CarUiPreference(mContext);
131                 preference.setTitle(info.getFullNameNative());
132                 // Only locales with multiple sublocales needs to show the chevron, since in those
133                 // cases, the user needs to navigate to the child fragment to select the sublocale.
134                 Set<LocaleStore.LocaleInfo> subLocales = LocaleStore.getLevelLocales(
135                         mContext,
136                         ignorables,
137                         info,
138                         /* translatedOnly */ true);
139                 preference.setShowChevron(subLocales.size() > 1);
140                 LocaleUtil.setLocaleArgument(preference, info);
141                 return preference;
142             default:
143                 LOG.d("Attempting to get unknown type: " + type);
144                 throw new IllegalStateException("Unknown locale list item type");
145         }
146     }
147 
148     /**
149      * Creates an instance of {@link SuggestedLocaleAdapter} with a locale
150      * {@link LocaleStore.LocaleInfo} that is scoped to a parent locale if a parent locale is
151      * provided.
152      */
createSuggestedLocaleAdapter(Context context, Set<LocaleStore.LocaleInfo> localeInfoSet, @Nullable LocaleStore.LocaleInfo parent)153     private static SuggestedLocaleAdapter createSuggestedLocaleAdapter(Context context,
154             Set<LocaleStore.LocaleInfo> localeInfoSet, @Nullable LocaleStore.LocaleInfo parent) {
155         boolean countryMode = (parent != null);
156         Locale displayLocale = countryMode ? parent.getLocale() : Locale.getDefault();
157         SuggestedLocaleAdapter adapter = new SuggestedLocaleAdapter(localeInfoSet, countryMode);
158         LocaleHelper.LocaleInfoComparator comp =
159                 new LocaleHelper.LocaleInfoComparator(displayLocale, countryMode);
160         adapter.sort(comp);
161         adapter.setDisplayLocale(context, displayLocale);
162         return adapter;
163     }
164 }
165