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