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 android.annotation.UserIdInt;
22 import android.content.Context;
23 import android.content.res.Resources;
24 import android.graphics.Bitmap;
25 import android.os.Handler;
26 import android.os.RemoteException;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.provider.Settings;
30 import android.util.Log;
31 import android.view.IWindowManager;
32 import android.widget.ImageView;
33 import android.widget.TextView;
34 
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.settingslib.drawable.CircleFramedDrawable;
38 import com.android.systemui.R;
39 import com.android.systemui.car.window.OverlayViewController;
40 import com.android.systemui.car.window.OverlayViewGlobalStateController;
41 import com.android.systemui.dagger.SysUISingleton;
42 import com.android.systemui.dagger.qualifiers.Main;
43 import com.android.systemui.util.concurrency.DelayableExecutor;
44 
45 import javax.inject.Inject;
46 
47 /**
48  * Handles showing and hiding UserSwitchTransitionView that is mounted to SystemUiOverlayWindow.
49  */
50 @SysUISingleton
51 public class UserSwitchTransitionViewController extends OverlayViewController {
52     private static final String TAG = "UserSwitchTransition";
53     private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true";
54     private static final boolean DEBUG = false;
55 
56     private final Context mContext;
57     private final Resources mResources;
58     private final DelayableExecutor mMainExecutor;
59     private final UserManager mUserManager;
60     private final IWindowManager mWindowManagerService;
61     private final int mWindowShownTimeoutMs;
62     private final Runnable mWindowShownTimeoutCallback = () -> {
63         if (DEBUG) {
64             Log.w(TAG, "Window was not hidden within " + getWindowShownTimeoutMs() + " ms, so it"
65                     + "was hidden by mWindowShownTimeoutCallback.");
66         }
67 
68         handleHide();
69     };
70 
71     @GuardedBy("this")
72     private boolean mShowing;
73     private int mPreviousUserId = UserHandle.USER_NULL;
74     private Runnable mCancelRunnable;
75 
76     @Inject
UserSwitchTransitionViewController( Context context, @Main Resources resources, @Main DelayableExecutor delayableExecutor, UserManager userManager, IWindowManager windowManagerService, OverlayViewGlobalStateController overlayViewGlobalStateController)77     public UserSwitchTransitionViewController(
78             Context context,
79             @Main Resources resources,
80             @Main DelayableExecutor delayableExecutor,
81             UserManager userManager,
82             IWindowManager windowManagerService,
83             OverlayViewGlobalStateController overlayViewGlobalStateController) {
84 
85         super(R.id.user_switching_dialog_stub, overlayViewGlobalStateController);
86 
87         mContext = context;
88         mResources = resources;
89         mMainExecutor = delayableExecutor;
90         mUserManager = userManager;
91         mWindowManagerService = windowManagerService;
92         mWindowShownTimeoutMs = mResources.getInteger(
93                 R.integer.config_userSwitchTransitionViewShownTimeoutMs);
94     }
95 
96     @Override
getInsetTypesToFit()97     protected int getInsetTypesToFit() {
98         return 0;
99     }
100 
101     /**
102      * Makes the user switch transition view appear and draws the content inside of it if a user
103      * that is different from the previous user is provided and if the dialog is not already
104      * showing.
105      */
handleShow(@serIdInt int newUserId)106     void handleShow(@UserIdInt int newUserId) {
107         mMainExecutor.execute(() -> {
108             if (mPreviousUserId == newUserId || mShowing) return;
109             mShowing = true;
110             try {
111                 mWindowManagerService.setSwitchingUser(true);
112                 mWindowManagerService.lockNow(null);
113             } catch (RemoteException e) {
114                 Log.e(TAG, "unable to notify window manager service regarding user switch");
115             }
116 
117             start();
118             populateDialog(mPreviousUserId, newUserId);
119             // next time a new user is selected, this current new user will be the previous user.
120             mPreviousUserId = newUserId;
121             // In case the window is still showing after WINDOW_SHOWN_TIMEOUT_MS, then hide the
122             // window and log a warning message.
123             mCancelRunnable = mMainExecutor.executeDelayed(mWindowShownTimeoutCallback,
124                     mWindowShownTimeoutMs);
125         });
126     }
127 
handleHide()128     void handleHide() {
129         if (!mShowing) return;
130         mMainExecutor.execute(() -> {
131             mShowing = false;
132             stop();
133             if (mCancelRunnable != null) {
134                 mCancelRunnable.run();
135             }
136         });
137     }
138 
139     @VisibleForTesting
getWindowShownTimeoutMs()140     int getWindowShownTimeoutMs() {
141         return mWindowShownTimeoutMs;
142     }
143 
populateDialog(@serIdInt int previousUserId, @UserIdInt int newUserId)144     private void populateDialog(@UserIdInt int previousUserId, @UserIdInt int newUserId) {
145         drawUserIcon(newUserId);
146         populateLoadingText(previousUserId, newUserId);
147     }
148 
drawUserIcon(int newUserId)149     private void drawUserIcon(int newUserId) {
150         Bitmap bitmap = mUserManager.getUserIcon(newUserId);
151         if (bitmap != null) {
152             CircleFramedDrawable drawable = CircleFramedDrawable.getInstance(mContext, bitmap);
153             ((ImageView) getLayout().findViewById(R.id.user_loading_avatar))
154                     .setImageDrawable(drawable);
155         }
156     }
157 
populateLoadingText(@serIdInt int previousUserId, @UserIdInt int newUserId)158     private void populateLoadingText(@UserIdInt int previousUserId, @UserIdInt int newUserId) {
159         TextView msgView = getLayout().findViewById(R.id.user_loading);
160 
161         boolean showInfo = ENABLE_DEVELOPER_MESSAGE_TRUE.equals(
162                 Settings.Global.getString(mContext.getContentResolver(),
163                         ENABLE_USER_SWITCH_DEVELOPER_MESSAGE));
164 
165         if (showInfo && mPreviousUserId != UserHandle.USER_NULL) {
166             msgView.setText(
167                     mResources.getString(R.string.car_loading_profile_developer_message,
168                             previousUserId, newUserId));
169         } else {
170             msgView.setText(mResources.getString(R.string.car_loading_profile));
171         }
172     }
173 }
174