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