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