• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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