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.systemui.car.userpicker;
18 
19 import static com.android.systemui.car.userpicker.UserEventManager.getMaxSupportedUsers;
20 
21 import android.annotation.IntDef;
22 import android.app.AlertDialog;
23 import android.app.Dialog;
24 import android.content.Context;
25 import android.util.Log;
26 import android.util.Slog;
27 import android.util.SparseArray;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.View.OnAttachStateChangeListener;
31 import android.view.Window;
32 import android.view.WindowInsets;
33 import android.view.WindowInsetsController;
34 import android.view.WindowManager;
35 import android.widget.TextView;
36 
37 import androidx.annotation.VisibleForTesting;
38 
39 import com.android.systemui.R;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 
44 import javax.inject.Inject;
45 
46 @UserPickerScope
47 final class DialogManager {
48     private static final String TAG = DialogManager.class.getSimpleName();
49     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
50 
51     static final int DIALOG_TYPE_SWITCHING = 0;
52     static final int DIALOG_TYPE_ADDING_USER = 1;
53     static final int DIALOG_TYPE_MAX_USER_COUNT_REACHED = 2;
54     static final int DIALOG_TYPE_CONFIRM_ADD_USER = 3;
55     static final int DIALOG_TYPE_CONFIRM_LOGOUT = 4;
56 
57     @IntDef(prefix = { "DIALOG_TYPE_" }, value = {
58         DIALOG_TYPE_SWITCHING,
59         DIALOG_TYPE_ADDING_USER,
60         DIALOG_TYPE_MAX_USER_COUNT_REACHED,
61         DIALOG_TYPE_CONFIRM_ADD_USER,
62         DIALOG_TYPE_CONFIRM_LOGOUT,
63     })
64     @Retention(RetentionPolicy.SOURCE)
65     public @interface DialogType {}
66 
67     @VisibleForTesting
68     final UserPickerDialogs mUserPickerDialogs = new UserPickerDialogs();
69 
70     private String mUserSwitchingMessage;
71     private String mUserAddingMessage;
72     private String mMaxUserLimitReachedTitle;
73     private String mMaxUserLimitReachedMessage;
74     private String mConfirmAddUserTitle;
75     private String mConfirmAddUserMessage;
76     private String mConfirmLogoutTitle;
77     private String mConfirmLogoutMessage;
78 
79     private Context mContext;
80     private int mDisplayId;
81 
82     @Inject
DialogManager()83     DialogManager() { }
84 
initContextFromView(View rootView)85     void initContextFromView(View rootView) {
86         // Dialog manager needs activity context, so sets view's context and display id.
87         mContext = rootView.getContext();
88         mDisplayId = mContext.getDisplayId();
89         updateTexts(mContext);
90     }
91 
updateTexts(Context context)92     void updateTexts(Context context) {
93         String lineSeparator = System.getProperty("line.separator");
94         mUserSwitchingMessage = context.getString(R.string.user_switching_message);
95         mUserAddingMessage = context.getString(R.string.user_adding_message);
96         mMaxUserLimitReachedTitle = context.getString(R.string.max_user_limit_reached_title);
97         mMaxUserLimitReachedMessage = context.getString(R.string.max_user_limit_reached_message);
98         mConfirmAddUserTitle = context.getString(R.string.confirm_add_user_title);
99         mConfirmAddUserMessage = context.getString(R.string.user_add_user_message_setup)
100                 .concat(lineSeparator)
101                 .concat(lineSeparator)
102                 .concat(context.getString(R.string.user_add_user_message_update));
103         mConfirmLogoutTitle = context.getString(R.string.user_logout_title);
104         mConfirmLogoutMessage = context.getString(R.string.user_logout_message);
105     }
106 
showDialog(@ialogType int type)107     void showDialog(@DialogType int type) {
108         showDialog(type, null);
109     }
110 
showDialog(@ialogType int type, Runnable positiveCallback)111     void showDialog(@DialogType int type, Runnable positiveCallback) {
112         showDialog(type, positiveCallback, null);
113     }
114 
showDialog(@ialogType int type, Runnable positiveCallback, Runnable cancelCallback)115     void showDialog(@DialogType int type, Runnable positiveCallback, Runnable cancelCallback) {
116         if (DEBUG) {
117             Slog.d(TAG, "showDialog: displayId=" + mDisplayId + " type=" + type);
118         }
119 
120         Dialog dialog = mUserPickerDialogs.get(type);
121         if (dialog == null) {
122             dialog = createAlertDialog(mContext, getDialogTitle(type), getDialogMessage(type),
123                     positiveCallback, cancelCallback, type);
124         }
125 
126         if (dialog != null) {
127             applyFlags(dialog);
128             mUserPickerDialogs.set(type, dialog);
129             dialog.show();
130         }
131     }
132 
dismissDialog(@ialogType int type)133     void dismissDialog(@DialogType int type) {
134         if (DEBUG) {
135             Slog.d(TAG, "dismissDialog: displayId=" + mDisplayId + " type=" + type);
136         }
137         Dialog dialog = mUserPickerDialogs.get(type);
138         if (dialog != null && dialog.isShowing()) {
139             dialog.dismiss();
140             mUserPickerDialogs.remove(type);
141         }
142     }
143 
removeDialog(@ialogType int type)144     void removeDialog(@DialogType int type) {
145         if (DEBUG) {
146             Slog.d(TAG, "removeDialog: displayId=" + mDisplayId + " type=" + type);
147         }
148         mUserPickerDialogs.remove(type);
149     }
150 
applyFlags(Dialog dialog)151     private void applyFlags(Dialog dialog) {
152         Window window = dialog.getWindow();
153         window.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
154         window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
155                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
156     }
157 
hideSystemBars(Dialog dialog)158     private void hideSystemBars(Dialog dialog) {
159         Window window = dialog.getWindow();
160         if (window.getDecorView() != null) {
161             WindowInsetsController insetsController = window.getInsetsController();
162             if (insetsController != null) {
163                 insetsController.hide(WindowInsets.Type.statusBars()
164                         | WindowInsets.Type.navigationBars());
165             }
166         }
167     }
168 
getDialogTitle(@ialogType int type)169     private String getDialogTitle(@DialogType int type) {
170         String title = "";
171         switch (type) {
172             case DIALOG_TYPE_MAX_USER_COUNT_REACHED:
173                 title = mMaxUserLimitReachedTitle;
174                 break;
175             case DIALOG_TYPE_CONFIRM_ADD_USER:
176                 title = mConfirmAddUserTitle;
177                 break;
178             case DIALOG_TYPE_CONFIRM_LOGOUT:
179                 title = mConfirmLogoutTitle;
180                 break;
181             default:
182                 Slog.w(TAG, "No dialog title for given type, " + typeToString(type));
183         }
184         return title;
185     }
186 
getDialogMessage(@ialogType int type)187     private String getDialogMessage(@DialogType int type) {
188         String message = "";
189         switch (type) {
190             case DIALOG_TYPE_SWITCHING:
191                 message = mUserSwitchingMessage;
192                 break;
193             case DIALOG_TYPE_ADDING_USER:
194                 message = mUserAddingMessage;
195                 break;
196             case DIALOG_TYPE_MAX_USER_COUNT_REACHED:
197                 message = String.format(mMaxUserLimitReachedMessage, getMaxSupportedUsers());
198                 break;
199             case DIALOG_TYPE_CONFIRM_ADD_USER:
200                 message = mConfirmAddUserMessage;
201                 break;
202             case DIALOG_TYPE_CONFIRM_LOGOUT:
203                 message = mConfirmLogoutMessage;
204                 break;
205             default:
206                 Slog.w(TAG, "No dialog message for given type, " + typeToString(type));
207         }
208         return message;
209     }
210 
typeToString(@ialogType int type)211     private String typeToString(@DialogType int type) {
212         switch (type) {
213             case DIALOG_TYPE_SWITCHING:
214                 return "DIALOG_TYPE_SWITCHING";
215             case DIALOG_TYPE_ADDING_USER:
216                 return "DIALOG_TYPE_ADDING_USER";
217             case DIALOG_TYPE_MAX_USER_COUNT_REACHED:
218                 return "DIALOG_TYPE_MAX_USER_COUNT_REACHED";
219             case DIALOG_TYPE_CONFIRM_ADD_USER:
220                 return "DIALOG_TYPE_CONFIRM_ADD_USER";
221             case DIALOG_TYPE_CONFIRM_LOGOUT:
222                 return "DIALOG_TYPE_CONFIRM_LOGOUT";
223             default:
224                 return "";
225         }
226     }
227 
createAlertDialog(Context context, String title, String message, Runnable positiveCallback, Runnable cancelCallback, @DialogType int type)228     private AlertDialog createAlertDialog(Context context, String title, String message,
229                                           Runnable positiveCallback, Runnable cancelCallback,
230                                           @DialogType int type) {
231         AlertDialog.Builder builder = new AlertDialog.Builder(context,
232                 com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert);
233         TextView messageView = null;
234 
235         switch (type) {
236             case DIALOG_TYPE_MAX_USER_COUNT_REACHED:
237             case DIALOG_TYPE_CONFIRM_ADD_USER:
238             case DIALOG_TYPE_CONFIRM_LOGOUT:
239                 builder.setTitle(title);
240                 builder.setPositiveButton(android.R.string.ok, (d, w) -> {
241                     if (positiveCallback != null) {
242                         positiveCallback.run();
243                     }
244                 });
245                 if (positiveCallback != null) {
246                     builder.setNegativeButton(android.R.string.cancel, (d, w) -> {
247                         if (cancelCallback != null) {
248                             cancelCallback.run();
249                         }
250                     });
251                 }
252                 if (cancelCallback != null) {
253                     builder.setOnCancelListener(dialog -> cancelCallback.run());
254                 }
255                 builder.setOnDismissListener(d -> removeDialog(type));
256                 messageView = (TextView) LayoutInflater.from(context)
257                         .inflate(R.layout.user_picker_alert_dialog_normal_message, null);
258                 break;
259             case DIALOG_TYPE_ADDING_USER:
260             case DIALOG_TYPE_SWITCHING:
261                 messageView = (TextView) LayoutInflater.from(context)
262                         .inflate(R.layout.user_picker_alert_dialog_large_center_message, null);
263                 builder.setCancelable(false);
264                 break;
265         }
266 
267         messageView.setText(message);
268         builder.setView(messageView);
269 
270         final AlertDialog alertDialog = builder.create();
271         messageView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
272             @Override
273             public void onViewAttachedToWindow(View view) {
274                 // hide system bars on the message view attached to the window.
275                 // A decor view of alert dialog is instantiated when show() method is called.
276                 // So, we can not hide system bars because there is no decor view
277                 // before show() is called.
278                 hideSystemBars(alertDialog);
279             }
280 
281             @Override
282             public void onViewDetachedFromWindow(View view) {}
283         });
284         return alertDialog;
285     }
286 
clearAllDialogs()287     void clearAllDialogs() {
288         mUserPickerDialogs.clear();
289     }
290 
291     @VisibleForTesting
292     final class UserPickerDialogs {
293         final SparseArray<Dialog> mDialogs = new SparseArray<>();
294 
get(int type)295         Dialog get(int type) {
296             return mDialogs.get(type);
297         }
298 
set(int type, Dialog dialog)299         void set(int type, Dialog dialog) {
300             mDialogs.set(type, dialog);
301         }
302 
remove(int type)303         void remove(int type) {
304             mDialogs.remove(type);
305         }
306 
clear()307         void clear() {
308             for (int i = 0; i < mDialogs.size(); i++) {
309                 Dialog d = mDialogs.valueAt(i);
310                 if (d != null && d.isShowing()) {
311                     d.dismiss();
312                 }
313             }
314             mDialogs.clear();
315         }
316     }
317 }
318