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