1 /*
2  * Copyright (C) 2021 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.statusbar.policy;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.database.DataSetObserver;
22 import android.graphics.drawable.Drawable;
23 import android.graphics.drawable.LayerDrawable;
24 import android.os.UserHandle;
25 import android.text.TextUtils;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.view.accessibility.AccessibilityNodeInfo;
30 import android.widget.FrameLayout;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.logging.UiEventLogger;
34 import com.android.keyguard.KeyguardConstants;
35 import com.android.keyguard.KeyguardVisibilityHelper;
36 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
37 import com.android.settingslib.drawable.CircleFramedDrawable;
38 import com.android.systemui.animation.Expandable;
39 import com.android.systemui.dagger.qualifiers.Main;
40 import com.android.systemui.plugins.FalsingManager;
41 import com.android.systemui.plugins.statusbar.StatusBarStateController;
42 import com.android.systemui.qs.user.UserSwitchDialogController;
43 import com.android.systemui.res.R;
44 import com.android.systemui.statusbar.SysuiStatusBarStateController;
45 import com.android.systemui.statusbar.notification.AnimatableProperty;
46 import com.android.systemui.statusbar.notification.PropertyAnimator;
47 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
48 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
49 import com.android.systemui.statusbar.phone.DozeParameters;
50 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
51 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
52 import com.android.systemui.statusbar.phone.UserAvatarView;
53 import com.android.systemui.user.data.source.UserRecord;
54 import com.android.systemui.util.ViewController;
55 
56 import javax.inject.Inject;
57 
58 /**
59  * Manages the user switch on the Keyguard that is used for opening the QS user panel.
60  */
61 @KeyguardUserSwitcherScope
62 public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> {
63 
64     private static final String TAG = "KeyguardQsUserSwitchController";
65     private static final boolean DEBUG = KeyguardConstants.DEBUG;
66 
67     private static final AnimationProperties ANIMATION_PROPERTIES =
68             new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
69 
70     private final Context mContext;
71     private Resources mResources;
72     private final UserSwitcherController mUserSwitcherController;
73     private BaseUserSwitcherAdapter mAdapter;
74     private final KeyguardStateController mKeyguardStateController;
75     private final FalsingManager mFalsingManager;
76     protected final SysuiStatusBarStateController mStatusBarStateController;
77     private final ConfigurationController mConfigurationController;
78     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
79     private final UserSwitchDialogController mUserSwitchDialogController;
80     private final UiEventLogger mUiEventLogger;
81     @VisibleForTesting
82     UserAvatarView mUserAvatarView;
83     private View mUserAvatarViewWithBackground;
84     UserRecord mCurrentUser;
85     private boolean mIsKeyguardShowing;
86 
87     // State info for the user switch and keyguard
88     private int mBarState;
89 
90     private final StatusBarStateController.StateListener mStatusBarStateListener =
91             new StatusBarStateController.StateListener() {
92                 @Override
93                 public void onStateChanged(int newState) {
94                     boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
95                     boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
96                     int oldState = mBarState;
97                     mBarState = newState;
98 
99                     setKeyguardQsUserSwitchVisibility(
100                             newState,
101                             keyguardFadingAway,
102                             goingToFullShade,
103                             oldState);
104                 }
105             };
106 
107     private ConfigurationController.ConfigurationListener mConfigurationListener =
108             new ConfigurationController.ConfigurationListener() {
109 
110                 @Override
111                 public void onUiModeChanged() {
112                     // Force update when dark theme toggled. Otherwise, icon will not update
113                     // until it is clicked
114                     if (mIsKeyguardShowing) {
115                         updateView();
116                     }
117                 }
118             };
119 
120     private final KeyguardStateController.Callback mKeyguardStateCallback =
121             new KeyguardStateController.Callback() {
122                 @Override
123                 public void onUnlockedChanged() {
124                     updateKeyguardShowing(false /* forceViewUpdate */);
125                 }
126 
127                 @Override
128                 public void onKeyguardShowingChanged() {
129                     updateKeyguardShowing(false /* forceViewUpdate */);
130                 }
131 
132                 @Override
133                 public void onKeyguardFadingAwayChanged() {
134                     updateKeyguardShowing(false /* forceViewUpdate */);
135                 }
136             };
137 
138     @Inject
KeyguardQsUserSwitchController( FrameLayout view, Context context, @Main Resources resources, UserSwitcherController userSwitcherController, KeyguardStateController keyguardStateController, FalsingManager falsingManager, ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, UserSwitchDialogController userSwitchDialogController, UiEventLogger uiEventLogger)139     public KeyguardQsUserSwitchController(
140             FrameLayout view,
141             Context context,
142             @Main Resources resources,
143             UserSwitcherController userSwitcherController,
144             KeyguardStateController keyguardStateController,
145             FalsingManager falsingManager,
146             ConfigurationController configurationController,
147             SysuiStatusBarStateController statusBarStateController,
148             DozeParameters dozeParameters,
149             ScreenOffAnimationController screenOffAnimationController,
150             UserSwitchDialogController userSwitchDialogController,
151             UiEventLogger uiEventLogger) {
152         super(view);
153         if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
154         mContext = context;
155         mResources = resources;
156         mUserSwitcherController = userSwitcherController;
157         mKeyguardStateController = keyguardStateController;
158         mFalsingManager = falsingManager;
159         mConfigurationController = configurationController;
160         mStatusBarStateController = statusBarStateController;
161         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
162                 keyguardStateController, dozeParameters,
163                 screenOffAnimationController,  /* animateYPos= */ false,
164                 /* logBuffer= */ null);
165         mUserSwitchDialogController = userSwitchDialogController;
166         mUiEventLogger = uiEventLogger;
167     }
168 
169     @Override
onInit()170     protected void onInit() {
171         super.onInit();
172         if (DEBUG) Log.d(TAG, "onInit");
173         mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
174         mUserAvatarViewWithBackground = mView.findViewById(
175                 R.id.kg_multi_user_avatar_with_background);
176         mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
177             @Override
178             public View getView(int position, View convertView, ViewGroup parent) {
179                 return null;
180             }
181         };
182 
183         mUserAvatarView.setOnClickListener(v -> {
184             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
185                 return;
186             }
187             if (isListAnimating()) {
188                 return;
189             }
190 
191             // Tapping anywhere in the view will open the user switcher
192             mUiEventLogger.log(
193                     LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
194 
195             mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(),
196                     Expandable.fromView(mUserAvatarViewWithBackground));
197         });
198 
199         mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
200             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
201                 super.onInitializeAccessibilityNodeInfo(host, info);
202                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
203                         AccessibilityNodeInfo.ACTION_CLICK,
204                         mContext.getString(
205                                 R.string.accessibility_quick_settings_choose_user_action)));
206             }
207         });
208     }
209 
210     @Override
onViewAttached()211     protected void onViewAttached() {
212         if (DEBUG) Log.d(TAG, "onViewAttached");
213         mAdapter.registerDataSetObserver(mDataSetObserver);
214         mDataSetObserver.onChanged();
215         mStatusBarStateController.addCallback(mStatusBarStateListener);
216         mConfigurationController.addCallback(mConfigurationListener);
217         mKeyguardStateController.addCallback(mKeyguardStateCallback);
218         // Force update when view attached in case configuration changed while the view was detached
219         updateCurrentUser();
220         updateKeyguardShowing(true /* forceViewUpdate */);
221     }
222 
223     @Override
onViewDetached()224     protected void onViewDetached() {
225         if (DEBUG) Log.d(TAG, "onViewDetached");
226 
227         mAdapter.unregisterDataSetObserver(mDataSetObserver);
228         mStatusBarStateController.removeCallback(mStatusBarStateListener);
229         mConfigurationController.removeCallback(mConfigurationListener);
230         mKeyguardStateController.removeCallback(mKeyguardStateCallback);
231     }
232 
233     public final DataSetObserver mDataSetObserver = new DataSetObserver() {
234         @Override
235         public void onChanged() {
236             boolean userChanged = updateCurrentUser();
237             if (userChanged || (mIsKeyguardShowing && mUserAvatarView.isEmpty())) {
238                 updateView();
239             }
240         }
241     };
242 
clearAvatar()243     private void clearAvatar() {
244         if (DEBUG) Log.d(TAG, "clearAvatar");
245         mUserAvatarView.setAvatar(null);
246     }
247 
248     /**
249      * @param forceViewUpdate whether view should be updated regardless of whether
250      *                        keyguard-showing state changed
251      */
252     @VisibleForTesting
updateKeyguardShowing(boolean forceViewUpdate)253     void updateKeyguardShowing(boolean forceViewUpdate) {
254         boolean wasKeyguardShowing = mIsKeyguardShowing;
255         mIsKeyguardShowing = mKeyguardStateController.isShowing()
256                 || mKeyguardStateController.isKeyguardGoingAway();
257         if (wasKeyguardShowing == mIsKeyguardShowing && !forceViewUpdate) {
258             return;
259         }
260         if (DEBUG) {
261             Log.d(TAG, "updateKeyguardShowing:"
262                     + " mIsKeyguardShowing=" + mIsKeyguardShowing
263                     + " forceViewUpdate=" + forceViewUpdate);
264         }
265         if (mIsKeyguardShowing) {
266             updateView();
267         } else {
268             clearAvatar();
269         }
270     }
271 
272     /**
273      * @return true if the current user has changed
274      */
updateCurrentUser()275     private boolean updateCurrentUser() {
276         UserRecord previousUser = mCurrentUser;
277         mCurrentUser = null;
278         for (int i = 0; i < mAdapter.getCount(); i++) {
279             UserRecord r = mAdapter.getItem(i);
280             if (r.isCurrent) {
281                 mCurrentUser = r;
282                 return !mCurrentUser.equals(previousUser);
283             }
284         }
285         return mCurrentUser == null && previousUser != null;
286     }
287 
getContentDescription()288     private String getContentDescription() {
289         if (mCurrentUser != null && mCurrentUser.info != null
290                 && !TextUtils.isEmpty(mCurrentUser.info.name)) {
291             // If we know the current user's name, have TalkBack to announce "Signed in as [user
292             // name]" when the icon is selected
293             return mContext.getString(
294                     R.string.accessibility_quick_settings_user, mCurrentUser.info.name);
295         } else {
296             // As a fallback, have TalkBack announce "Switch user"
297             return mContext.getString(R.string.accessibility_multi_user_switch_switcher);
298         }
299     }
300 
updateView()301     private void updateView() {
302         if (DEBUG) Log.d(TAG, "updateView");
303         mUserAvatarView.setContentDescription(getContentDescription());
304         int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
305         mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
306     }
307 
getCurrentUserIcon()308     Drawable getCurrentUserIcon() {
309         Drawable drawable;
310         if (mCurrentUser == null || mCurrentUser.picture == null) {
311             if (mCurrentUser != null && mCurrentUser.isGuest) {
312                 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
313             } else {
314                 drawable = mContext.getDrawable(R.drawable.ic_avatar_user);
315             }
316             int iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
317             drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
318         } else {
319             int avatarSize = (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
320             drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
321         }
322 
323         Drawable bg = mContext.getDrawable(com.android.settingslib.R.drawable.user_avatar_bg);
324         drawable = new LayerDrawable(new Drawable[]{bg, drawable});
325         return drawable;
326     }
327 
328     /**
329      * Get the height of the keyguard user switcher view when closed.
330      */
getUserIconHeight()331     public int getUserIconHeight() {
332         return mUserAvatarView.getHeight();
333     }
334 
335     /**
336      * Set the visibility of the user avatar view based on some new state.
337      */
setKeyguardQsUserSwitchVisibility( int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState)338     public void setKeyguardQsUserSwitchVisibility(
339             int statusBarState,
340             boolean keyguardFadingAway,
341             boolean goingToFullShade,
342             int oldStatusBarState) {
343         mKeyguardVisibilityHelper.setViewVisibility(
344                 statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
345     }
346 
347     /**
348      * Update position of the view with an optional animation
349      */
updatePosition(int x, int y, boolean animate)350     public void updatePosition(int x, int y, boolean animate) {
351         PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate);
352         PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
353                 ANIMATION_PROPERTIES, animate);
354     }
355 
356     /**
357      * Set keyguard user avatar view alpha.
358      */
setAlpha(float alpha)359     public void setAlpha(float alpha) {
360         if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
361             mView.setAlpha(alpha);
362         }
363     }
364 
isListAnimating()365     private boolean isListAnimating() {
366         return mKeyguardVisibilityHelper.isVisibilityAnimating();
367     }
368 }
369