1 /** 2 * Copyright (C) 2025 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.settings.localepicker; 18 19 import static com.android.settings.localepicker.RegionAndNumberingSystemPickerFragment.EXTRA_APP_PACKAGE_NAME; 20 import static com.android.settings.localepicker.RegionAndNumberingSystemPickerFragment.EXTRA_IS_NUMBERING_SYSTEM; 21 import static com.android.settings.localepicker.RegionAndNumberingSystemPickerFragment.EXTRA_TARGET_LOCALE; 22 23 import android.app.Activity; 24 import android.app.FragmentManager; 25 import android.app.settings.SettingsEnums; 26 import android.content.Context; 27 import android.os.Bundle; 28 import android.text.TextUtils; 29 import android.util.ArrayMap; 30 import android.util.Log; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 import androidx.annotation.VisibleForTesting; 35 import androidx.core.app.NotificationCompat; 36 import androidx.preference.Preference; 37 import androidx.preference.PreferenceCategory; 38 import androidx.preference.PreferenceScreen; 39 40 import com.android.internal.app.AppLocaleCollector; 41 import com.android.internal.app.LocaleStore; 42 import com.android.settings.R; 43 import com.android.settings.applications.manageapplications.ManageApplicationsUtil; 44 import com.android.settings.core.BasePreferenceController; 45 import com.android.settings.core.SubSettingLauncher; 46 import com.android.settings.spa.SpaActivity; 47 import com.android.settingslib.core.instrumentation.Instrumentable; 48 import com.android.settingslib.widget.SelectorWithWidgetPreference; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.Map; 53 import java.util.Set; 54 import java.util.stream.Collectors; 55 56 /** A controller for handling supported locale of app. */ 57 public class AppLocaleAllListPreferenceController extends 58 BasePreferenceController implements LocaleListSearchCallback { 59 private static final String TAG = "AppLocaleAllListPreferenceController"; 60 private static final String KEY_PREFERENCE_CATEGORY_APP_LANGUAGE_ALL_SUPPORTED = 61 "app_language_all_supported_category"; 62 private static final String KEY_PREFERENCE_APP_LOCALE_LIST = "app_locale_list"; 63 private static final String KEY_PREFERENCE_CATEGORY_ADD_LANGUAGE_ALL_SUPPORTED = 64 "system_language_all_supported_category"; 65 66 private Activity mActivity; 67 @SuppressWarnings("NullAway") 68 private PreferenceCategory mPreferenceCategory; 69 private Set<LocaleStore.LocaleInfo> mLocaleList; 70 private List<LocaleStore.LocaleInfo> mLocaleOptions; 71 private Map<String, Preference> mSupportedPreferences; 72 private boolean mIsCountryMode; 73 private boolean mIsNumberingSystemMode; 74 @Nullable 75 private LocaleStore.LocaleInfo mParentLocale; 76 private AppLocaleCollector mAppLocaleCollector; 77 @SuppressWarnings("NullAway") 78 private String mPackageName; 79 80 @SuppressWarnings("NullAway") AppLocaleAllListPreferenceController(@onNull Context context, @NonNull String preferenceKey)81 public AppLocaleAllListPreferenceController(@NonNull Context context, 82 @NonNull String preferenceKey) { 83 super(context, preferenceKey); 84 } 85 86 @SuppressWarnings("NullAway") AppLocaleAllListPreferenceController(@onNull Context context, @NonNull String preferenceKey, @Nullable String packageName, boolean isNumberingSystemMode, @NonNull LocaleStore.LocaleInfo parentLocale, @NonNull Activity activity, @NonNull AppLocaleCollector appLocaleCollector)87 public AppLocaleAllListPreferenceController(@NonNull Context context, 88 @NonNull String preferenceKey, @Nullable String packageName, 89 boolean isNumberingSystemMode, @NonNull LocaleStore.LocaleInfo parentLocale, 90 @NonNull Activity activity, @NonNull AppLocaleCollector appLocaleCollector) { 91 super(context, preferenceKey); 92 mPackageName = packageName; 93 mIsNumberingSystemMode = isNumberingSystemMode; 94 mParentLocale = parentLocale; 95 mIsCountryMode = mParentLocale != null; 96 mActivity = activity; 97 mAppLocaleCollector = appLocaleCollector; 98 } 99 100 @Override displayPreference(@onNull PreferenceScreen screen)101 public void displayPreference(@NonNull PreferenceScreen screen) { 102 super.displayPreference(screen); 103 mPreferenceCategory = screen.findPreference( 104 (mIsNumberingSystemMode || mIsCountryMode) 105 ? KEY_PREFERENCE_CATEGORY_ADD_LANGUAGE_ALL_SUPPORTED 106 : KEY_PREFERENCE_CATEGORY_APP_LANGUAGE_ALL_SUPPORTED); 107 108 mAppLocaleCollector = new AppLocaleCollector(mContext, mPackageName); 109 mSupportedPreferences = new ArrayMap<>(); 110 mLocaleOptions = new ArrayList<>(); 111 updatePreferences(); 112 } 113 updatePreferences()114 private void updatePreferences() { 115 if (mPreferenceCategory == null) { 116 Log.d(TAG, "updatePreferences, mPreferenceCategory is null"); 117 return; 118 } 119 120 List<LocaleStore.LocaleInfo> result = LocaleUtils.getSortedLocaleList( 121 getSupportedLocaleList(), mIsCountryMode); 122 if (mIsCountryMode) { 123 mPreferenceCategory.setTitle( 124 mContext.getString(R.string.all_supported_locales_regions_title)); 125 } 126 final Map<String, Preference> existingSupportedPreferences = mSupportedPreferences; 127 mSupportedPreferences = new ArrayMap<>(); 128 setupSupportedPreference(result, existingSupportedPreferences); 129 for (Preference pref : existingSupportedPreferences.values()) { 130 mPreferenceCategory.removePreference(pref); 131 } 132 } 133 134 @Override onSearchListChanged(@onNull List<LocaleStore.LocaleInfo> newList, @Nullable CharSequence prefix)135 public void onSearchListChanged(@NonNull List<LocaleStore.LocaleInfo> newList, 136 @Nullable CharSequence prefix) { 137 mPreferenceCategory.removeAll(); 138 mSupportedPreferences.clear(); 139 final Map<String, Preference> existingSupportedPreferences = mSupportedPreferences; 140 List<LocaleStore.LocaleInfo> sortedList = getSupportedLocaleList(); 141 newList = LocaleUtils.getSortedLocaleFromSearchList(newList, sortedList, mIsCountryMode); 142 setupSupportedPreference(newList, existingSupportedPreferences); 143 } 144 145 @VisibleForTesting setupSupportedPreference(List<LocaleStore.LocaleInfo> localeInfoList, Map<String, Preference> existingSupportedPreferences)146 void setupSupportedPreference(List<LocaleStore.LocaleInfo> localeInfoList, 147 Map<String, Preference> existingSupportedPreferences) { 148 if (mIsNumberingSystemMode) { 149 mPreferenceCategory.setTitle(""); 150 } 151 152 for (LocaleStore.LocaleInfo locale : localeInfoList) { 153 Preference pref = existingSupportedPreferences.remove(locale.getId()); 154 if (pref == null) { 155 pref = new Preference(mContext); 156 mPreferenceCategory.addPreference(pref); 157 setupPreference(pref, locale); 158 } 159 } 160 mPreferenceCategory.setVisible(mPreferenceCategory.getPreferenceCount() > 0); 161 } 162 setupPreference(Preference pref, LocaleStore.LocaleInfo locale)163 private void setupPreference(Preference pref, LocaleStore.LocaleInfo locale) { 164 String localeName = mIsCountryMode ? locale.getFullCountryNameNative() 165 : locale.getFullNameNative(); 166 pref.setTitle(localeName); 167 pref.setKey(locale.toString()); 168 pref.setOnPreferenceClickListener(clickedPref -> { 169 // TODO: b/388199937 - Switch to correct fragment. 170 Log.d(TAG, "setupPreference: mIsCountryMode = " + mIsCountryMode); 171 switchFragment(mContext, locale, shouldShowAppLanguage(locale)); 172 mActivity.finish(); 173 return true; 174 }); 175 mSupportedPreferences.put(locale.getId(), pref); 176 } 177 178 @Override getAvailabilityStatus()179 public int getAvailabilityStatus() { 180 return AVAILABLE; 181 } 182 183 @VisibleForTesting switchFragment(Context context, LocaleStore.LocaleInfo localeInfo, boolean shouldShowAppLanguage)184 void switchFragment(Context context, LocaleStore.LocaleInfo localeInfo, 185 boolean shouldShowAppLanguage) { 186 if (shouldShowAppLanguage) { 187 LocaleUtils.onLocaleSelected(mContext, localeInfo, mPackageName); 188 } else { 189 String extraKey = EXTRA_TARGET_LOCALE; 190 String fragmentName = RegionAndNumberingSystemPickerFragment.class.getCanonicalName(); 191 final Bundle extra = new Bundle(); 192 extra.putSerializable(extraKey, localeInfo); 193 extra.putBoolean(EXTRA_IS_NUMBERING_SYSTEM, localeInfo.hasNumberingSystems()); 194 extra.putString(EXTRA_APP_PACKAGE_NAME, mPackageName); 195 new SubSettingLauncher(context) 196 .setDestination(fragmentName) 197 .setSourceMetricsCategory(Instrumentable.METRICS_CATEGORY_UNKNOWN) 198 .setArguments(extra) 199 .launch(); 200 } 201 } 202 203 @VisibleForTesting shouldShowAppLanguage(LocaleStore.LocaleInfo localeInfo)204 boolean shouldShowAppLanguage(LocaleStore.LocaleInfo localeInfo) { 205 boolean isSystemLocale = localeInfo.isSystemLocale(); 206 boolean isRegionLocale = localeInfo.getParent() != null; 207 boolean mayHaveDifferentNumberingSystem = localeInfo.hasNumberingSystems(); 208 mLocaleList = mAppLocaleCollector.getSupportedLocaleList(localeInfo, 209 false, (localeInfo != null)); 210 Log.d(TAG, 211 "shouldShowAppLanguage: isSystemLocale = " + isSystemLocale + ", isRegionLocale = " 212 + isRegionLocale + ", mayHaveDifferentNumberingSystem = " 213 + mayHaveDifferentNumberingSystem + ", isNumberingMode = " 214 + mIsNumberingSystemMode); 215 216 return mLocaleList.size() == 1 || isSystemLocale || mIsNumberingSystemMode 217 || (isRegionLocale && !mayHaveDifferentNumberingSystem); 218 } 219 getSupportedLocaleList()220 protected List<LocaleStore.LocaleInfo> getSupportedLocaleList() { 221 setupLocaleList(); 222 if (mLocaleList != null && !mLocaleList.isEmpty()) { 223 mLocaleOptions.addAll( 224 mLocaleList.stream().filter(localeInfo -> !localeInfo.isSuggested()).collect( 225 Collectors.toList())); 226 } else { 227 Log.d(TAG, "Can not get supported locales because the locale list is null or empty."); 228 } 229 return mLocaleOptions; 230 } 231 setupLocaleList()232 private void setupLocaleList() { 233 mLocaleList = mAppLocaleCollector.getSupportedLocaleList(mParentLocale, 234 false, mIsCountryMode); 235 mLocaleOptions.clear(); 236 } 237 238 @Override getPreferenceKey()239 public @NonNull String getPreferenceKey() { 240 return KEY_PREFERENCE_APP_LOCALE_LIST; 241 } 242 } 243