1 /* 2 * Copyright (C) 2024 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.regionalpreferences; 18 19 import static android.app.settings.SettingsEnums.ACTION_CHANGE_PREFERRED_LANGUAGE_REGION_POSITIVE_BTN_CLICKED; 20 import static android.app.settings.SettingsEnums.ACTION_CHANGE_PREFERRED_LANGUAGE_REGION_NEGATIVE_BTN_CLICKED; 21 import static android.app.settings.SettingsEnums.ACTION_CHANGE_REGION_DIALOG_NEGATIVE_BTN_CLICKED; 22 import static android.app.settings.SettingsEnums.ACTION_CHANGE_REGION_DIALOG_POSITIVE_BTN_CLICKED; 23 import static android.app.settings.SettingsEnums.CHANGE_REGION_DIALOG; 24 25 import android.app.Dialog; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.os.Bundle; 29 import android.os.LocaleList; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.widget.TextView; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 import androidx.annotation.VisibleForTesting; 38 import androidx.appcompat.app.AlertDialog; 39 40 import com.android.internal.app.LocalePicker; 41 import com.android.internal.app.LocaleStore; 42 import com.android.settings.R; 43 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 44 import com.android.settings.overlay.FeatureFactory; 45 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 46 47 import java.util.Locale; 48 import java.util.Set; 49 50 /** 51 * Create a dialog for system region events. 52 */ 53 public class RegionDialogFragment extends InstrumentedDialogFragment { 54 55 public static final String ARG_CALLING_PAGE = "arg_calling_page"; 56 public static final int CALLING_PAGE_LANGUAGE_CHOOSE_A_REGION = 0; 57 public static final int CALLING_PAGE_REGIONAL_PREFERENCES_REGION_PICKER = 1; 58 public static final int DIALOG_CHANGE_SYSTEM_LOCALE_REGION = 1; 59 public static final int DIALOG_CHANGE_PREFERRED_LOCALE_REGION = 2; 60 public static final String ARG_DIALOG_TYPE = "arg_dialog_type"; 61 public static final String ARG_TARGET_LOCALE = "arg_target_locale"; 62 public static final String ARG_REPLACED_TARGET_LOCALE = "arg_replaced_target_locale"; 63 64 private static final String TAG = "RegionDialogFragment"; 65 66 /** 67 * Use this factory method to create a new instance of 68 * this fragment using the provided parameters. 69 * 70 * @return A new instance of fragment RegionDialogFragment. 71 */ 72 @NonNull newInstance()73 public static RegionDialogFragment newInstance() { 74 return new RegionDialogFragment(); 75 } 76 77 @Override getMetricsCategory()78 public int getMetricsCategory() { 79 return CHANGE_REGION_DIALOG; 80 } 81 82 @NonNull 83 @Override onCreateDialog(@ullable Bundle savedInstanceState)84 public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 85 // TODO(385834414): Migrate to use MaterialAlertDialogBuilder 86 RegionDialogController controller = getRegionDialogController(getContext(), this); 87 RegionDialogController.DialogContent dialogContent = controller.getDialogContent(); 88 ViewGroup viewGroup = (ViewGroup) LayoutInflater.from(getContext()).inflate( 89 R.layout.locale_dialog, null); 90 setDialogTitle(viewGroup, dialogContent.mTitle); 91 setDialogMessage(viewGroup, dialogContent.mMessage); 92 AlertDialog.Builder builder = new AlertDialog.Builder(getContext()).setView(viewGroup); 93 if (!dialogContent.mPositiveButton.isEmpty()) { 94 builder.setPositiveButton(dialogContent.mPositiveButton, controller); 95 } 96 if (!dialogContent.mNegativeButton.isEmpty()) { 97 builder.setNegativeButton(dialogContent.mNegativeButton, controller); 98 } 99 return builder.create(); 100 } 101 setDialogTitle(View root, String content)102 private static void setDialogTitle(View root, String content) { 103 TextView titleView = root.findViewById(R.id.dialog_title); 104 if (titleView == null) { 105 return; 106 } 107 titleView.setText(content); 108 } 109 setDialogMessage(View root, String content)110 private static void setDialogMessage(View root, String content) { 111 TextView textView = root.findViewById(R.id.dialog_msg); 112 if (textView == null) { 113 return; 114 } 115 textView.setText(content); 116 } 117 118 @VisibleForTesting getRegionDialogController(Context context, RegionDialogFragment dialogFragment)119 RegionDialogController getRegionDialogController(Context context, 120 RegionDialogFragment dialogFragment) { 121 return new RegionDialogController(context, dialogFragment); 122 } 123 124 class RegionDialogController implements DialogInterface.OnClickListener { 125 private final Context mContext; 126 private final int mDialogType; 127 private final int mCallingPage; 128 private final LocaleStore.LocaleInfo mLocaleInfo; 129 private final Locale mReplacedLocale; 130 private final MetricsFeatureProvider mMetricsFeatureProvider; 131 RegionDialogController( @onNull Context context, @NonNull RegionDialogFragment dialogFragment)132 RegionDialogController( 133 @NonNull Context context, @NonNull RegionDialogFragment dialogFragment) { 134 mContext = context; 135 Bundle arguments = dialogFragment.getArguments(); 136 mDialogType = arguments.getInt(ARG_DIALOG_TYPE); 137 mCallingPage = arguments.getInt(ARG_CALLING_PAGE); 138 mLocaleInfo = (LocaleStore.LocaleInfo) arguments.getSerializable(ARG_TARGET_LOCALE); 139 mReplacedLocale = (Locale) arguments.getSerializable(ARG_REPLACED_TARGET_LOCALE); 140 mMetricsFeatureProvider = 141 FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); 142 } 143 144 @Override onClick(@onNull DialogInterface dialog, int which)145 public void onClick(@NonNull DialogInterface dialog, int which) { 146 if (mDialogType == DIALOG_CHANGE_SYSTEM_LOCALE_REGION 147 || mDialogType == DIALOG_CHANGE_PREFERRED_LOCALE_REGION) { 148 if (which == DialogInterface.BUTTON_POSITIVE) { 149 updateRegion(mLocaleInfo.getLocale().toLanguageTag()); 150 mMetricsFeatureProvider.action( 151 mContext, 152 mDialogType == DIALOG_CHANGE_SYSTEM_LOCALE_REGION 153 ? ACTION_CHANGE_REGION_DIALOG_POSITIVE_BTN_CLICKED 154 : ACTION_CHANGE_PREFERRED_LANGUAGE_REGION_POSITIVE_BTN_CLICKED, 155 mCallingPage); 156 dismiss(); 157 if (getActivity() != null) { 158 getActivity().finish(); 159 } 160 } else { 161 mMetricsFeatureProvider.action( 162 mContext, 163 mDialogType == DIALOG_CHANGE_SYSTEM_LOCALE_REGION 164 ? ACTION_CHANGE_REGION_DIALOG_NEGATIVE_BTN_CLICKED 165 : ACTION_CHANGE_PREFERRED_LANGUAGE_REGION_NEGATIVE_BTN_CLICKED, 166 mCallingPage); 167 dismiss(); 168 } 169 } 170 } 171 172 @VisibleForTesting getDialogContent()173 DialogContent getDialogContent() { 174 DialogContent dialogContent = new DialogContent(); 175 switch (mDialogType) { 176 case DIALOG_CHANGE_SYSTEM_LOCALE_REGION: 177 dialogContent.mTitle = String.format(mContext.getString( 178 R.string.title_change_system_region), 179 mLocaleInfo.getLocale().getDisplayCountry()); 180 dialogContent.mMessage = mContext.getString( 181 R.string.desc_notice_device_region_change, 182 Locale.getDefault().getDisplayLanguage()); 183 dialogContent.mPositiveButton = mContext.getString( 184 R.string.button_label_confirmation_of_system_locale_change); 185 dialogContent.mNegativeButton = mContext.getString(R.string.cancel); 186 break; 187 case DIALOG_CHANGE_PREFERRED_LOCALE_REGION: 188 dialogContent.mTitle = String.format(mContext.getString( 189 R.string.title_change_system_region), 190 mLocaleInfo.getLocale().getDisplayCountry()); 191 dialogContent.mMessage = mContext.getString( 192 R.string.desc_notice_device_region_change_for_preferred_language, 193 mLocaleInfo.getFullNameNative(), 194 LocaleStore.getLocaleInfo(mReplacedLocale).getFullNameNative()); 195 dialogContent.mPositiveButton = mContext.getString( 196 R.string.button_label_confirmation_of_system_locale_change); 197 dialogContent.mNegativeButton = mContext.getString(R.string.cancel); 198 break; 199 default: 200 break; 201 } 202 return dialogContent; 203 } 204 updateRegion(String selectedLanguageTag)205 private void updateRegion(String selectedLanguageTag) { 206 Locale[] newLocales = getUpdatedLocales(Locale.forLanguageTag(selectedLanguageTag)); 207 LocalePicker.updateLocales(new LocaleList(newLocales)); 208 } 209 getUpdatedLocales(Locale selectedLocale)210 private Locale[] getUpdatedLocales(Locale selectedLocale) { 211 LocaleList localeList = LocaleList.getDefault(); 212 Locale[] newLocales = new Locale[localeList.size()]; 213 for (int i = 0; i < localeList.size(); i++) { 214 Locale target = localeList.get(i); 215 if (sameLanguageAndScript(selectedLocale, target)) { 216 newLocales[i] = appendLocaleExtension(selectedLocale); 217 } else { 218 newLocales[i] = localeList.get(i); 219 } 220 } 221 return newLocales; 222 } 223 appendLocaleExtension(Locale selectedLocale)224 private Locale appendLocaleExtension(Locale selectedLocale) { 225 Locale systemLocale = Locale.getDefault(); 226 Set<Character> extensionKeys = systemLocale.getExtensionKeys(); 227 Locale.Builder builder = new Locale.Builder(); 228 builder.setLocale(selectedLocale); 229 if (!extensionKeys.isEmpty()) { 230 for (Character extKey : extensionKeys) { 231 builder.setExtension(extKey, systemLocale.getExtension(extKey)); 232 } 233 } 234 return builder.build(); 235 } 236 sameLanguageAndScript(Locale source, Locale target)237 private static boolean sameLanguageAndScript(Locale source, Locale target) { 238 String sourceLanguage = source.getLanguage(); 239 String targetLanguage = target.getLanguage(); 240 String sourceLocaleScript = source.getScript(); 241 String targetLocaleScript = target.getScript(); 242 if (sourceLanguage.equals(targetLanguage)) { 243 if (!sourceLocaleScript.isEmpty() && !targetLocaleScript.isEmpty()) { 244 return sourceLocaleScript.equals(targetLocaleScript); 245 } 246 return true; 247 } 248 return false; 249 } 250 251 @VisibleForTesting 252 static class DialogContent { 253 String mTitle = ""; 254 String mMessage = ""; 255 String mPositiveButton = ""; 256 String mNegativeButton = ""; 257 } 258 } 259 } 260