1 /* 2 * Copyright (C) 2023 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 android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT; 20 21 import android.app.Activity; 22 import android.app.Dialog; 23 import android.app.settings.SettingsEnums; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.os.Bundle; 28 import android.util.Log; 29 import android.view.LayoutInflater; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.widget.TextView; 33 import android.window.OnBackInvokedCallback; 34 import android.window.OnBackInvokedDispatcher; 35 36 import androidx.annotation.NonNull; 37 import androidx.annotation.VisibleForTesting; 38 import androidx.appcompat.app.AlertDialog; 39 40 import com.android.internal.app.LocaleStore; 41 import com.android.settings.R; 42 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 43 import com.android.settings.overlay.FeatureFactory; 44 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 45 46 /** 47 * Create a dialog for system locale events. 48 */ 49 public class LocaleDialogFragment extends InstrumentedDialogFragment { 50 private static final String TAG = LocaleDialogFragment.class.getSimpleName(); 51 52 static final int DIALOG_CONFIRM_SYSTEM_DEFAULT = 1; 53 static final int DIALOG_NOT_AVAILABLE_LOCALE = 2; 54 static final int DIALOG_ADD_SYSTEM_LOCALE = 3; 55 56 static final String ARG_DIALOG_TYPE = "arg_dialog_type"; 57 static final String ARG_TARGET_LOCALE = "arg_target_locale"; 58 static final String ARG_SHOW_DIALOG = "arg_show_dialog"; 59 60 private boolean mShouldKeepDialog; 61 private AlertDialog mAlertDialog; 62 private OnBackInvokedDispatcher mBackDispatcher; 63 64 private OnBackInvokedCallback mBackCallback = () -> { 65 Log.d(TAG, "Do not back to previous page if the dialog is displaying."); 66 }; 67 newInstance()68 public static LocaleDialogFragment newInstance() { 69 return new LocaleDialogFragment(); 70 } 71 72 @Override getMetricsCategory()73 public int getMetricsCategory() { 74 int dialogType = getArguments().getInt(ARG_DIALOG_TYPE); 75 switch (dialogType) { 76 case DIALOG_CONFIRM_SYSTEM_DEFAULT: 77 return SettingsEnums.DIALOG_SYSTEM_LOCALE_CHANGE; 78 case DIALOG_NOT_AVAILABLE_LOCALE: 79 return SettingsEnums.DIALOG_SYSTEM_LOCALE_UNAVAILABLE; 80 default: 81 return SettingsEnums.DIALOG_SYSTEM_LOCALE_CHANGE; 82 } 83 } 84 85 @Override onSaveInstanceState(Bundle outState)86 public void onSaveInstanceState(Bundle outState) { 87 super.onSaveInstanceState(outState); 88 outState.putBoolean(ARG_SHOW_DIALOG, mShouldKeepDialog); 89 } 90 91 @Override onCreateDialog(Bundle savedInstanceState)92 public Dialog onCreateDialog(Bundle savedInstanceState) { 93 if (savedInstanceState != null) { 94 Bundle arguments = getArguments(); 95 int type = arguments.getInt(ARG_DIALOG_TYPE); 96 mShouldKeepDialog = savedInstanceState.getBoolean(ARG_SHOW_DIALOG, false); 97 // Keep the dialog if user rotates the device, otherwise close the confirm system 98 // default dialog only when user changes the locale. 99 if ((type == DIALOG_CONFIRM_SYSTEM_DEFAULT || type == DIALOG_ADD_SYSTEM_LOCALE) 100 && !mShouldKeepDialog) { 101 dismiss(); 102 } 103 } 104 105 mShouldKeepDialog = true; 106 LocaleListEditor parentFragment = (LocaleListEditor) getParentFragment(); 107 LocaleDialogController controller = getLocaleDialogController(getContext(), this, 108 parentFragment); 109 LocaleDialogController.DialogContent dialogContent = controller.getDialogContent(); 110 ViewGroup viewGroup = (ViewGroup) LayoutInflater.from(getContext()).inflate( 111 R.layout.locale_dialog, null); 112 setDialogTitle(viewGroup, dialogContent.mTitle); 113 setDialogMessage(viewGroup, dialogContent.mMessage); 114 115 AlertDialog.Builder builder = new AlertDialog.Builder(getContext()) 116 .setView(viewGroup); 117 if (!dialogContent.mPositiveButton.isEmpty()) { 118 builder.setPositiveButton(dialogContent.mPositiveButton, controller); 119 } 120 if (!dialogContent.mNegativeButton.isEmpty()) { 121 builder.setNegativeButton(dialogContent.mNegativeButton, controller); 122 } 123 mAlertDialog = builder.create(); 124 getOnBackInvokedDispatcher().registerOnBackInvokedCallback(PRIORITY_DEFAULT, mBackCallback); 125 mAlertDialog.setCanceledOnTouchOutside(false); 126 mAlertDialog.setOnDismissListener(dialogInterface -> { 127 mAlertDialog.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback( 128 mBackCallback); 129 }); 130 131 return mAlertDialog; 132 } 133 setDialogTitle(View root, String content)134 private static void setDialogTitle(View root, String content) { 135 TextView titleView = root.findViewById(R.id.dialog_title); 136 if (titleView == null) { 137 return; 138 } 139 titleView.setText(content); 140 } 141 setDialogMessage(View root, String content)142 private static void setDialogMessage(View root, String content) { 143 TextView textView = root.findViewById(R.id.dialog_msg); 144 if (textView == null) { 145 return; 146 } 147 textView.setText(content); 148 } 149 150 @VisibleForTesting getBackInvokedCallback()151 public OnBackInvokedCallback getBackInvokedCallback() { 152 return mBackCallback; 153 } 154 155 @VisibleForTesting setBackDispatcher(OnBackInvokedDispatcher dispatcher)156 public void setBackDispatcher(OnBackInvokedDispatcher dispatcher) { 157 mBackDispatcher = dispatcher; 158 } 159 160 @VisibleForTesting getOnBackInvokedDispatcher()161 public OnBackInvokedDispatcher getOnBackInvokedDispatcher() { 162 if (mBackDispatcher != null) { 163 return mBackDispatcher; 164 } else { 165 return mAlertDialog.getOnBackInvokedDispatcher(); 166 } 167 } 168 169 @VisibleForTesting getLocaleDialogController(Context context, LocaleDialogFragment dialogFragment, LocaleListEditor parentFragment)170 LocaleDialogController getLocaleDialogController(Context context, 171 LocaleDialogFragment dialogFragment, LocaleListEditor parentFragment) { 172 return new LocaleDialogController(context, dialogFragment, parentFragment); 173 } 174 175 class LocaleDialogController implements DialogInterface.OnClickListener { 176 private final Context mContext; 177 private final int mDialogType; 178 private final LocaleStore.LocaleInfo mLocaleInfo; 179 private final MetricsFeatureProvider mMetricsFeatureProvider; 180 181 private LocaleListEditor mParent; 182 LocaleDialogController( @onNull Context context, @NonNull LocaleDialogFragment dialogFragment, LocaleListEditor parentFragment)183 LocaleDialogController( 184 @NonNull Context context, @NonNull LocaleDialogFragment dialogFragment, 185 LocaleListEditor parentFragment) { 186 mContext = context; 187 Bundle arguments = dialogFragment.getArguments(); 188 mDialogType = arguments.getInt(ARG_DIALOG_TYPE); 189 mLocaleInfo = (LocaleStore.LocaleInfo) arguments.getSerializable(ARG_TARGET_LOCALE); 190 mMetricsFeatureProvider = 191 FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); 192 mParent = parentFragment; 193 } 194 195 @Override onClick(DialogInterface dialog, int which)196 public void onClick(DialogInterface dialog, int which) { 197 if (mDialogType == DIALOG_CONFIRM_SYSTEM_DEFAULT 198 || mDialogType == DIALOG_ADD_SYSTEM_LOCALE) { 199 int result = Activity.RESULT_CANCELED; 200 boolean changed = false; 201 if (which == DialogInterface.BUTTON_POSITIVE) { 202 result = Activity.RESULT_OK; 203 changed = true; 204 } 205 Intent intent = new Intent(); 206 Bundle bundle = new Bundle(); 207 bundle.putInt(ARG_DIALOG_TYPE, mDialogType); 208 bundle.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE, mLocaleInfo); 209 intent.putExtras(bundle); 210 mParent.onActivityResult(mDialogType, result, intent); 211 mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CHANGE_LANGUAGE, 212 changed); 213 } 214 mShouldKeepDialog = false; 215 } 216 217 @VisibleForTesting getDialogContent()218 DialogContent getDialogContent() { 219 DialogContent dialogContent = new DialogContent(); 220 switch (mDialogType) { 221 case DIALOG_CONFIRM_SYSTEM_DEFAULT: 222 dialogContent.mTitle = String.format(mContext.getString( 223 R.string.title_change_system_locale), mLocaleInfo.getFullNameNative()); 224 dialogContent.mMessage = mContext.getString( 225 R.string.desc_notice_device_locale_settings_change); 226 dialogContent.mPositiveButton = mContext.getString( 227 R.string.button_label_confirmation_of_system_locale_change); 228 dialogContent.mNegativeButton = mContext.getString(R.string.cancel); 229 break; 230 case DIALOG_NOT_AVAILABLE_LOCALE: 231 dialogContent.mTitle = String.format(mContext.getString( 232 R.string.title_unavailable_locale), mLocaleInfo.getFullNameNative()); 233 dialogContent.mMessage = mContext.getString(R.string.desc_unavailable_locale); 234 dialogContent.mPositiveButton = mContext.getString(R.string.okay); 235 break; 236 case DIALOG_ADD_SYSTEM_LOCALE: 237 dialogContent.mTitle = String.format(mContext.getString( 238 R.string.title_system_locale_addition), 239 mLocaleInfo.getFullNameNative()); 240 dialogContent.mMessage = mContext.getString( 241 R.string.desc_system_locale_addition); 242 dialogContent.mPositiveButton = mContext.getString(R.string.add); 243 dialogContent.mNegativeButton = mContext.getString(R.string.cancel); 244 break; 245 default: 246 break; 247 } 248 return dialogContent; 249 } 250 251 @VisibleForTesting 252 static class DialogContent { 253 String mTitle = ""; 254 String mMessage = ""; 255 String mPositiveButton = ""; 256 String mNegativeButton = ""; 257 } 258 } 259 } 260