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; 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, UserIconProvider userIconProvider, 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 UserIconProvider userIconProvider, 104 IWindowManager windowManagerService, 105 OverlayViewGlobalStateController overlayViewGlobalStateController) { 106 107 super(R.id.user_switching_dialog_stub, overlayViewGlobalStateController); 108 109 mContext = context; 110 mResources = resources; 111 mMainExecutor = delayableExecutor; 112 mActivityManager = activityManager; 113 mUserManager = userManager; 114 mUserIconProvider = userIconProvider; 115 mWindowManagerService = windowManagerService; 116 mKeyguardManager = context.getSystemService(KeyguardManager.class); 117 mWindowShownTimeoutMs = mResources.getInteger( 118 R.integer.config_userSwitchTransitionViewShownTimeoutMs); 119 } 120 121 @Override getInsetTypesToFit()122 protected int getInsetTypesToFit() { 123 return 0; 124 } 125 126 @Override showInternal()127 protected void showInternal() { 128 populateDialog(mPreviousUserId, mNewUserId); 129 super.showInternal(); 130 } 131 132 /** 133 * Makes the user switch transition view appear and draws the content inside of it if a user 134 * that is different from the previous user is provided and if the dialog is not already 135 * showing. 136 */ handleShow(@serIdInt int newUserId)137 void handleShow(@UserIdInt int newUserId) { 138 mMainExecutor.execute(() -> { 139 if (mPreviousUserId == newUserId || mTransitionViewShowing) return; 140 mTransitionViewShowing = true; 141 try { 142 mWindowManagerService.setSwitchingUser(true); 143 if (!refactorGetCurrentUser()) { 144 mWindowManagerService.lockNow(null); 145 } 146 } catch (RemoteException e) { 147 Log.e(TAG, "unable to notify window manager service regarding user switch"); 148 } 149 150 mNewUserId = newUserId; 151 start(); 152 // In case the window is still showing after WINDOW_SHOWN_TIMEOUT_MS, then hide the 153 // window and log a warning message. 154 mCancelRunnable = mMainExecutor.executeDelayed(mWindowShownTimeoutCallback, 155 mWindowShownTimeoutMs); 156 157 if (refactorGetCurrentUser() && mKeyguardManager.isDeviceSecure(newUserId)) { 158 // Setup keyguard timeout but don't lock the device just yet. 159 // The device cannot be locked until we receive a user switching event - otherwise 160 // the KeyguardViewMediator will not have the new userId. 161 setupKeyguardShownTimeout(); 162 } 163 }); 164 } 165 handleSwitching(int newUserId)166 void handleSwitching(int newUserId) { 167 if (!refactorGetCurrentUser()) { 168 return; 169 } 170 if (!mKeyguardManager.isDeviceSecure(newUserId)) { 171 return; 172 } 173 mMainExecutor.execute(() -> { 174 try { 175 if (DEBUG) { 176 Log.d(TAG, "Notifying WM to lock device"); 177 } 178 mWindowManagerService.lockNow(null); 179 } catch (RemoteException e) { 180 throw new RuntimeException("Error notifying WM of lock state", e); 181 } 182 }); 183 } 184 handleHide()185 void handleHide() { 186 if (!mTransitionViewShowing) return; 187 if (mPendingKeyguardShow) { 188 if (DEBUG) { 189 Log.d(TAG, "Delaying hide while waiting for keyguard to show"); 190 } 191 // Prevent hiding transition view until device is locked - otherwise the home screen 192 // may temporarily be exposed. 193 mPendingHideForKeyguardShown = true; 194 return; 195 } 196 mMainExecutor.execute(() -> { 197 // next time a new user is selected, this current new user will be the previous user. 198 mPreviousUserId = mNewUserId; 199 mTransitionViewShowing = false; 200 stop(); 201 if (mCancelRunnable != null) { 202 mCancelRunnable.run(); 203 } 204 }); 205 } 206 207 @VisibleForTesting getWindowShownTimeoutMs()208 int getWindowShownTimeoutMs() { 209 return mWindowShownTimeoutMs; 210 } 211 populateDialog(@serIdInt int previousUserId, @UserIdInt int newUserId)212 private void populateDialog(@UserIdInt int previousUserId, @UserIdInt int newUserId) { 213 drawUserIcon(newUserId); 214 populateLoadingText(previousUserId, newUserId); 215 } 216 drawUserIcon(int newUserId)217 private void drawUserIcon(int newUserId) { 218 Drawable userIcon = mUserIconProvider.getDrawableWithBadge(newUserId); 219 ((ImageView) getLayout().findViewById(R.id.user_loading_avatar)) 220 .setImageDrawable(userIcon); 221 } 222 populateLoadingText(@serIdInt int previousUserId, @UserIdInt int newUserId)223 private void populateLoadingText(@UserIdInt int previousUserId, @UserIdInt int newUserId) { 224 TextView msgView = getLayout().findViewById(R.id.user_loading); 225 226 boolean showInfo = ENABLE_DEVELOPER_MESSAGE_TRUE.equals( 227 Settings.Global.getString(mContext.getContentResolver(), 228 ENABLE_USER_SWITCH_DEVELOPER_MESSAGE)); 229 230 if (showInfo && mPreviousUserId != UserHandle.USER_NULL) { 231 msgView.setText( 232 mResources.getString(R.string.car_loading_profile_developer_message, 233 previousUserId, newUserId)); 234 } else { 235 // Show the switchingFromUserMessage if it was set. 236 String switchingFromUserMessage = 237 mActivityManager.getSwitchingFromUserMessage(previousUserId); 238 msgView.setText(switchingFromUserMessage != null ? switchingFromUserMessage 239 : mResources.getString(R.string.car_loading_profile)); 240 } 241 } 242 243 /** 244 * Wait for keyguard to be shown before hiding this blocking view. 245 * This method does the following (in-order): 246 * - Checks if the keyguard is already locked (and if so, do nothing else). 247 * - Register a KeyguardLockedStateListener to be notified when the keyguard is locked. 248 * - Start a 20 second timeout for keyguard to be shown. If it is not shown within this 249 * timeframe, SysUI/WM is in a bad state - crash SysUI and allow it to recover on restart. 250 */ 251 @VisibleForTesting setupKeyguardShownTimeout()252 void setupKeyguardShownTimeout() { 253 if (!userSwitchKeyguardShownTimeout()) { 254 return; 255 } 256 if (mPendingKeyguardShow) { 257 Log.w(TAG, "Attempted to setup timeout while pending keyguard show"); 258 return; 259 } 260 try { 261 if (mWindowManagerService.isKeyguardLocked()) { 262 return; 263 } 264 } catch (RemoteException e) { 265 throw new RuntimeException("Unable to get current lock state from WM", e); 266 } 267 if (DEBUG) { 268 Log.d(TAG, "Setting up keyguard show timeout"); 269 } 270 mPendingKeyguardShow = true; 271 // The executor's cancel runnable for the keyguard timeout (not the timeout itself) 272 AtomicReference<Runnable> cancelKeyguardTimeout = new AtomicReference<>(); 273 KeyguardManager.KeyguardLockedStateListener keyguardLockedStateListener = 274 new KeyguardManager.KeyguardLockedStateListener() { 275 @Override 276 public void onKeyguardLockedStateChanged(boolean isKeyguardLocked) { 277 if (DEBUG) { 278 Log.d(TAG, "Keyguard state change keyguardLocked=" + isKeyguardLocked); 279 } 280 if (isKeyguardLocked) { 281 mPendingKeyguardShow = false; 282 Runnable cancelTimeoutRunnable = cancelKeyguardTimeout.getAndSet(null); 283 if (cancelTimeoutRunnable != null) { 284 cancelTimeoutRunnable.run(); 285 } 286 mKeyguardManager.removeKeyguardLockedStateListener(this); 287 if (mPendingHideForKeyguardShown) { 288 mPendingHideForKeyguardShown = false; 289 handleHide(); 290 } 291 } 292 } 293 }; 294 mKeyguardManager.addKeyguardLockedStateListener(mMainExecutor, keyguardLockedStateListener); 295 296 Runnable keyguardTimeoutRunnable = () -> { 297 mKeyguardManager.removeKeyguardLockedStateListener(keyguardLockedStateListener); 298 // Keyguard did not show up in the expected timeframe - this indicates something is very 299 // wrong. Crash SystemUI and allow it to recover on re-initialization. 300 throw new RuntimeException( 301 String.format("Keyguard was not shown in %d seconds", KEYGUARD_SHOW_TIMEOUT)); 302 }; 303 cancelKeyguardTimeout.set(mMainExecutor.executeDelayed(keyguardTimeoutRunnable, 304 KEYGUARD_SHOW_TIMEOUT, TimeUnit.SECONDS)); 305 } 306 } 307