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.R;
39 import com.android.systemui.animation.Expandable;
40 import com.android.systemui.dagger.qualifiers.Main;
41 import com.android.systemui.plugins.FalsingManager;
42 import com.android.systemui.plugins.statusbar.StatusBarStateController;
43 import com.android.systemui.qs.user.UserSwitchDialogController;
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, /* logBuffer= */ null);
164         mUserSwitchDialogController = userSwitchDialogController;
165         mUiEventLogger = uiEventLogger;
166     }
167 
168     @Override
onInit()169     protected void onInit() {
170         super.onInit();
171         if (DEBUG) Log.d(TAG, "onInit");
172         mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
173         mUserAvatarViewWithBackground = mView.findViewById(
174                 R.id.kg_multi_user_avatar_with_background);
175         mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
176             @Override
177             public View getView(int position, View convertView, ViewGroup parent) {
178                 return null;
179             }
180         };
181 
182         mUserAvatarView.setOnClickListener(v -> {
183             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
184                 return;
185             }
186             if (isListAnimating()) {
187                 return;
188             }
189 
190             // Tapping anywhere in the view will open the user switcher
191             mUiEventLogger.log(
192                     LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
193 
194             mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(),
195                     Expandable.fromView(mUserAvatarViewWithBackground));
196         });
197 
198         mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
199             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
200                 super.onInitializeAccessibilityNodeInfo(host, info);
201                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
202                         AccessibilityNodeInfo.ACTION_CLICK,
203                         mContext.getString(
204                                 R.string.accessibility_quick_settings_choose_user_action)));
205             }
206         });
207     }
208 
209     @Override
onViewAttached()210     protected void onViewAttached() {
211         if (DEBUG) Log.d(TAG, "onViewAttached");
212         mAdapter.registerDataSetObserver(mDataSetObserver);
213         mDataSetObserver.onChanged();
214         mStatusBarStateController.addCallback(mStatusBarStateListener);
215         mConfigurationController.addCallback(mConfigurationListener);
216         mKeyguardStateController.addCallback(mKeyguardStateCallback);
217         // Force update when view attached in case configuration changed while the view was detached
218         updateCurrentUser();
219         updateKeyguardShowing(true /* forceViewUpdate */);
220     }
221 
222     @Override
onViewDetached()223     protected void onViewDetached() {
224         if (DEBUG) Log.d(TAG, "onViewDetached");
225 
226         mAdapter.unregisterDataSetObserver(mDataSetObserver);
227         mStatusBarStateController.removeCallback(mStatusBarStateListener);
228         mConfigurationController.removeCallback(mConfigurationListener);
229         mKeyguardStateController.removeCallback(mKeyguardStateCallback);
230     }
231 
232     public final DataSetObserver mDataSetObserver = new DataSetObserver() {
233         @Override
234         public void onChanged() {
235             boolean userChanged = updateCurrentUser();
236             if (userChanged || (mIsKeyguardShowing && mUserAvatarView.isEmpty())) {
237                 updateView();
238             }
239         }
240     };
241 
clearAvatar()242     private void clearAvatar() {
243         if (DEBUG) Log.d(TAG, "clearAvatar");
244         mUserAvatarView.setAvatar(null);
245     }
246 
247     /**
248      * @param forceViewUpdate whether view should be updated regardless of whether
249      *                        keyguard-showing state changed
250      */
251     @VisibleForTesting
updateKeyguardShowing(boolean forceViewUpdate)252     void updateKeyguardShowing(boolean forceViewUpdate) {
253         boolean wasKeyguardShowing = mIsKeyguardShowing;
254         mIsKeyguardShowing = mKeyguardStateController.isShowing()
255                 || mKeyguardStateController.isKeyguardGoingAway();
256         if (wasKeyguardShowing == mIsKeyguardShowing && !forceViewUpdate) {
257             return;
258         }
259         if (DEBUG) {
260             Log.d(TAG, "updateKeyguardShowing:"
261                     + " mIsKeyguardShowing=" + mIsKeyguardShowing
262                     + " forceViewUpdate=" + forceViewUpdate);
263         }
264         if (mIsKeyguardShowing) {
265             updateView();
266         } else {
267             clearAvatar();
268         }
269     }
270 
271     /**
272      * @return true if the current user has changed
273      */
updateCurrentUser()274     private boolean updateCurrentUser() {
275         UserRecord previousUser = mCurrentUser;
276         mCurrentUser = null;
277         for (int i = 0; i < mAdapter.getCount(); i++) {
278             UserRecord r = mAdapter.getItem(i);
279             if (r.isCurrent) {
280                 mCurrentUser = r;
281                 return !mCurrentUser.equals(previousUser);
282             }
283         }
284         return mCurrentUser == null && previousUser != null;
285     }
286 
getContentDescription()287     private String getContentDescription() {
288         if (mCurrentUser != null && mCurrentUser.info != null
289                 && !TextUtils.isEmpty(mCurrentUser.info.name)) {
290             // If we know the current user's name, have TalkBack to announce "Signed in as [user
291             // name]" when the icon is selected
292             return mContext.getString(
293                     R.string.accessibility_quick_settings_user, mCurrentUser.info.name);
294         } else {
295             // As a fallback, have TalkBack announce "Switch user"
296             return mContext.getString(R.string.accessibility_multi_user_switch_switcher);
297         }
298     }
299 
updateView()300     private void updateView() {
301         if (DEBUG) Log.d(TAG, "updateView");
302         mUserAvatarView.setContentDescription(getContentDescription());
303         int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
304         mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
305     }
306 
getCurrentUserIcon()307     Drawable getCurrentUserIcon() {
308         Drawable drawable;
309         if (mCurrentUser == null || mCurrentUser.picture == null) {
310             if (mCurrentUser != null && mCurrentUser.isGuest) {
311                 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
312             } else {
313                 drawable = mContext.getDrawable(R.drawable.ic_avatar_user);
314             }
315             int iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
316             drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
317         } else {
318             int avatarSize = (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
319             drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
320         }
321 
322         Drawable bg = mContext.getDrawable(R.drawable.user_avatar_bg);
323         drawable = new LayerDrawable(new Drawable[]{bg, drawable});
324         return drawable;
325     }
326 
327     /**
328      * Get the height of the keyguard user switcher view when closed.
329      */
getUserIconHeight()330     public int getUserIconHeight() {
331         return mUserAvatarView.getHeight();
332     }
333 
334     /**
335      * Set the visibility of the user avatar view based on some new state.
336      */
setKeyguardQsUserSwitchVisibility( int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState)337     public void setKeyguardQsUserSwitchVisibility(
338             int statusBarState,
339             boolean keyguardFadingAway,
340             boolean goingToFullShade,
341             int oldStatusBarState) {
342         mKeyguardVisibilityHelper.setViewVisibility(
343                 statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
344     }
345 
346     /**
347      * Update position of the view with an optional animation
348      */
updatePosition(int x, int y, boolean animate)349     public void updatePosition(int x, int y, boolean animate) {
350         PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate);
351         PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
352                 ANIMATION_PROPERTIES, animate);
353     }
354 
355     /**
356      * Set keyguard user avatar view alpha.
357      */
setAlpha(float alpha)358     public void setAlpha(float alpha) {
359         if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
360             mView.setAlpha(alpha);
361         }
362     }
363 
isListAnimating()364     private boolean isListAnimating() {
365         return mKeyguardVisibilityHelper.isVisibilityAnimating();
366     }
367 }
368