1 /* 2 * Copyright (C) 2020 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.userswitcher; 18 19 import static android.car.settings.CarSettings.Global.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE; 20 21 import static com.android.systemui.Flags.refactorGetCurrentUser; 22 import static com.android.systemui.car.Flags.userSwitchKeyguardShownTimeout; 23 24 import android.annotation.UserIdInt; 25 import android.app.ActivityManager; 26 import android.app.KeyguardManager; 27 import android.content.Context; 28 import android.content.res.Resources; 29 import android.graphics.drawable.Drawable; 30 import android.os.Build; 31 import android.os.RemoteException; 32 import android.os.UserHandle; 33 import android.os.UserManager; 34 import android.provider.Settings; 35 import android.util.Log; 36 import android.view.IWindowManager; 37 import android.widget.ImageView; 38 import android.widget.TextView; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.systemui.R; 42 import com.android.systemui.car.window.OverlayViewController; 43 import com.android.systemui.car.window.OverlayViewGlobalStateController; 44 import com.android.systemui.dagger.SysUISingleton; 45 import com.android.systemui.dagger.qualifiers.Main; 46 import com.android.systemui.util.concurrency.DelayableExecutor; 47 48 import java.util.concurrent.TimeUnit; 49 import java.util.concurrent.atomic.AtomicReference; 50 51 import javax.inject.Inject; 52 53 /** 54 * Handles showing and hiding UserSwitchTransitionView that is mounted to SystemUiOverlayWindow. 55 */ 56 @SysUISingleton 57 public class UserSwitchTransitionViewController extends OverlayViewController { 58 private static final String TAG = "UserSwitchTransition"; 59 private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true"; 60 private static final boolean DEBUG = Build.IS_DEBUGGABLE; 61 // Amount of time to wait for keyguard to show before restarting SysUI (in seconds) 62 private static final int KEYGUARD_SHOW_TIMEOUT = 20; 63 64 private final Context mContext; 65 private final Resources mResources; 66 private final DelayableExecutor mMainExecutor; 67 private final ActivityManager mActivityManager; 68 private final UserManager mUserManager; 69 private final IWindowManager mWindowManagerService; 70 private final KeyguardManager mKeyguardManager; 71 private final UserIconProvider mUserIconProvider = new UserIconProvider(); 72 private final int mWindowShownTimeoutMs; 73 private final Runnable mWindowShownTimeoutCallback = () -> { 74 if (DEBUG) { 75 Log.w(TAG, "Window was not hidden within " + getWindowShownTimeoutMs() + " ms, so it" 76 + "was hidden by mWindowShownTimeoutCallback."); 77 } 78 79 handleHide(); 80 }; 81 82 // Whether the user switch transition is currently showing - only modified on main executor 83 private boolean mTransitionViewShowing; 84 // State when waiting for the keyguard to be shown as part of the user switch 85 // Only modified on main executor 86 private boolean mPendingKeyguardShow; 87 // State when the a hide was attempted but ignored because the keyguard has not been shown yet 88 // - once the keyguard is shown, the view should be hidden. 89 // Only modified on main executor 90 private boolean mPendingHideForKeyguardShown; 91 92 private int mNewUserId = UserHandle.USER_NULL; 93 private int mPreviousUserId = UserHandle.USER_NULL; 94 private Runnable mCancelRunnable; 95 96 @Inject UserSwitchTransitionViewController( Context context, @Main Resources resources, @Main DelayableExecutor delayableExecutor, ActivityManager activityManager, UserManager userManager, IWindowManager windowManagerService, OverlayViewGlobalStateController overlayViewGlobalStateController)97 public UserSwitchTransitionViewController( 98 Context context, 99 @Main Resources resources, 100 @Main DelayableExecutor delayableExecutor, 101 ActivityManager activityManager, 102 UserManager userManager, 103 IWindowManager windowManagerService, 104 OverlayViewGlobalStateController overlayViewGlobalStateController) { 105 106 super(R.id.user_switching_dialog_stub, overlayViewGlobalStateController); 107 108 mContext = context; 109 mResources = resources; 110 mMainExecutor = delayableExecutor; 111 mActivityManager = activityManager; 112 mUserManager = userManager; 113 mWindowManagerService = windowManagerService; 114 mKeyguardManager = context.getSystemService(KeyguardManager.class); 115 mWindowShownTimeoutMs = mResources.getInteger( 116 R.integer.config_userSwitchTransitionViewShownTimeoutMs); 117 } 118 119 @Override getInsetTypesToFit()120 protected int getInsetTypesToFit() { 121 return 0; 122 } 123 124 @Override showInternal()125 protected void showInternal() { 126 populateDialog(mPreviousUserId, mNewUserId); 127 super.showInternal(); 128 } 129 130 /** 131 * Makes the user switch transition view appear and draws the content inside of it if a user 132 * that is different from the previous user is provided and if the dialog is not already 133 * showing. 134 */ handleShow(@serIdInt int newUserId)135 void handleShow(@UserIdInt int newUserId) { 136 mMainExecutor.execute(() -> { 137 if (mPreviousUserId == newUserId || mTransitionViewShowing) return; 138 mTransitionViewShowing = true; 139 try { 140 mWindowManagerService.setSwitchingUser(true); 141 if (!refactorGetCurrentUser()) { 142 mWindowManagerService.lockNow(null); 143 } 144 } catch (RemoteException e) { 145 Log.e(TAG, "unable to notify window manager service regarding user switch"); 146 } 147 148 mNewUserId = newUserId; 149 start(); 150 // In case the window is still showing after WINDOW_SHOWN_TIMEOUT_MS, then hide the 151 // window and log a warning message. 152 mCancelRunnable = mMainExecutor.executeDelayed(mWindowShownTimeoutCallback, 153 mWindowShownTimeoutMs); 154 155 if (refactorGetCurrentUser() && mKeyguardManager.isDeviceSecure(newUserId)) { 156 // Setup keyguard timeout but don't lock the device just yet. 157 // The device cannot be locked until we receive a user switching event - otherwise 158 // the KeyguardViewMediator will not have the new userId. 159 setupKeyguardShownTimeout(); 160 } 161 }); 162 } 163 handleSwitching(int newUserId)164 void handleSwitching(int newUserId) { 165 if (!refactorGetCurrentUser()) { 166 return; 167 } 168 if (!mKeyguardManager.isDeviceSecure(newUserId)) { 169 return; 170 } 171 mMainExecutor.execute(() -> { 172 try { 173 if (DEBUG) { 174 Log.d(TAG, "Notifying WM to lock device"); 175 } 176 mWindowManagerService.lockNow(null); 177 } catch (RemoteException e) { 178 throw new RuntimeException("Error notifying WM of lock state", e); 179 } 180 }); 181 } 182 handleHide()183 void handleHide() { 184 if (!mTransitionViewShowing) return; 185 if (mPendingKeyguardShow) { 186 if (DEBUG) { 187 Log.d(TAG, "Delaying hide while waiting for keyguard to show"); 188 } 189 // Prevent hiding transition view until device is locked - otherwise the home screen 190 // may temporarily be exposed. 191 mPendingHideForKeyguardShown = true; 192 return; 193 } 194 mMainExecutor.execute(() -> { 195 // next time a new user is selected, this current new user will be the previous user. 196 mPreviousUserId = mNewUserId; 197 mTransitionViewShowing = false; 198 stop(); 199 if (mCancelRunnable != null) { 200 mCancelRunnable.run(); 201 } 202 }); 203 } 204 205 @VisibleForTesting getWindowShownTimeoutMs()206 int getWindowShownTimeoutMs() { 207 return mWindowShownTimeoutMs; 208 } 209 populateDialog(@serIdInt int previousUserId, @UserIdInt int newUserId)210 private void populateDialog(@UserIdInt int previousUserId, @UserIdInt int newUserId) { 211 drawUserIcon(newUserId); 212 populateLoadingText(previousUserId, newUserId); 213 } 214 drawUserIcon(int newUserId)215 private void drawUserIcon(int newUserId) { 216 Drawable userIcon = mUserIconProvider.getDrawableWithBadge(mContext, 217 mUserManager.getUserInfo(newUserId)); 218 ((ImageView) getLayout().findViewById(R.id.user_loading_avatar)) 219 .setImageDrawable(userIcon); 220 } 221 populateLoadingText(@serIdInt int previousUserId, @UserIdInt int newUserId)222 private void populateLoadingText(@UserIdInt int previousUserId, @UserIdInt int newUserId) { 223 TextView msgView = getLayout().findViewById(R.id.user_loading); 224 225 boolean showInfo = ENABLE_DEVELOPER_MESSAGE_TRUE.equals( 226 Settings.Global.getString(mContext.getContentResolver(), 227 ENABLE_USER_SWITCH_DEVELOPER_MESSAGE)); 228 229 if (showInfo && mPreviousUserId != UserHandle.USER_NULL) { 230 msgView.setText( 231 mResources.getString(R.string.car_loading_profile_developer_message, 232 previousUserId, newUserId)); 233 } else { 234 // Show the switchingFromUserMessage if it was set. 235 String switchingFromUserMessage = mActivityManager.getSwitchingFromUserMessage(); 236 msgView.setText(switchingFromUserMessage != null ? switchingFromUserMessage 237 : mResources.getString(R.string.car_loading_profile)); 238 } 239 } 240 241 /** 242 * Wait for keyguard to be shown before hiding this blocking view. 243 * This method does the following (in-order): 244 * - Checks if the keyguard is already locked (and if so, do nothing else). 245 * - Register a KeyguardLockedStateListener to be notified when the keyguard is locked. 246 * - Start a 20 second timeout for keyguard to be shown. If it is not shown within this 247 * timeframe, SysUI/WM is in a bad state - crash SysUI and allow it to recover on restart. 248 */ 249 @VisibleForTesting setupKeyguardShownTimeout()250 void setupKeyguardShownTimeout() { 251 if (!userSwitchKeyguardShownTimeout()) { 252 return; 253 } 254 if (mPendingKeyguardShow) { 255 Log.w(TAG, "Attempted to setup timeout while pending keyguard show"); 256 return; 257 } 258 try { 259 if (mWindowManagerService.isKeyguardLocked()) { 260 return; 261 } 262 } catch (RemoteException e) { 263 throw new RuntimeException("Unable to get current lock state from WM", e); 264 } 265 if (DEBUG) { 266 Log.d(TAG, "Setting up keyguard show timeout"); 267 } 268 mPendingKeyguardShow = true; 269 // The executor's cancel runnable for the keyguard timeout (not the timeout itself) 270 AtomicReference<Runnable> cancelKeyguardTimeout = new AtomicReference<>(); 271 KeyguardManager.KeyguardLockedStateListener keyguardLockedStateListener = 272 new KeyguardManager.KeyguardLockedStateListener() { 273 @Override 274 public void onKeyguardLockedStateChanged(boolean isKeyguardLocked) { 275 if (DEBUG) { 276 Log.d(TAG, "Keyguard state change keyguardLocked=" + isKeyguardLocked); 277 } 278 if (isKeyguardLocked) { 279 mPendingKeyguardShow = false; 280 Runnable cancelTimeoutRunnable = cancelKeyguardTimeout.getAndSet(null); 281 if (cancelTimeoutRunnable != null) { 282 cancelTimeoutRunnable.run(); 283 } 284 mKeyguardManager.removeKeyguardLockedStateListener(this); 285 if (mPendingHideForKeyguardShown) { 286 mPendingHideForKeyguardShown = false; 287 handleHide(); 288 } 289 } 290 } 291 }; 292 mKeyguardManager.addKeyguardLockedStateListener(mMainExecutor, keyguardLockedStateListener); 293 294 Runnable keyguardTimeoutRunnable = () -> { 295 mKeyguardManager.removeKeyguardLockedStateListener(keyguardLockedStateListener); 296 // Keyguard did not show up in the expected timeframe - this indicates something is very 297 // wrong. Crash SystemUI and allow it to recover on re-initialization. 298 throw new RuntimeException( 299 String.format("Keyguard was not shown in %d seconds", KEYGUARD_SHOW_TIMEOUT)); 300 }; 301 cancelKeyguardTimeout.set(mMainExecutor.executeDelayed(keyguardTimeoutRunnable, 302 KEYGUARD_SHOW_TIMEOUT, TimeUnit.SECONDS)); 303 } 304 } 305