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.keyguard.KeyguardConstants;
33 import com.android.keyguard.KeyguardVisibilityHelper;
34 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
35 import com.android.settingslib.drawable.CircleFramedDrawable;
36 import com.android.systemui.R;
37 import com.android.systemui.dagger.qualifiers.Main;
38 import com.android.systemui.flags.FeatureFlags;
39 import com.android.systemui.keyguard.ScreenLifecycle;
40 import com.android.systemui.plugins.FalsingManager;
41 import com.android.systemui.plugins.statusbar.StatusBarStateController;
42 import com.android.systemui.qs.tiles.UserDetailView;
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.NotificationPanelViewController;
51 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
52 import com.android.systemui.statusbar.phone.UserAvatarView;
53 import com.android.systemui.util.ViewController;
54 
55 import javax.inject.Inject;
56 import javax.inject.Provider;
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 final ScreenLifecycle mScreenLifecycle;
74     private UserSwitcherController.BaseUserAdapter mAdapter;
75     private final KeyguardStateController mKeyguardStateController;
76     private final FalsingManager mFalsingManager;
77     protected final SysuiStatusBarStateController mStatusBarStateController;
78     private final ConfigurationController mConfigurationController;
79     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
80     private final KeyguardUserDetailAdapter mUserDetailAdapter;
81     private final FeatureFlags mFeatureFlags;
82     private final UserSwitchDialogController mUserSwitchDialogController;
83     private NotificationPanelViewController mNotificationPanelViewController;
84     private UserAvatarView mUserAvatarView;
85     UserSwitcherController.UserRecord mCurrentUser;
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                     if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));
95 
96                     boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
97                     boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
98                     int oldState = mBarState;
99                     mBarState = newState;
100 
101                     setKeyguardQsUserSwitchVisibility(
102                             newState,
103                             keyguardFadingAway,
104                             goingToFullShade,
105                             oldState);
106                 }
107             };
108 
109     private ConfigurationController.ConfigurationListener
110             mConfigurationListener = new ConfigurationController.ConfigurationListener() {
111 
112                 @Override
113                 public void onUiModeChanged() {
114                     updateView(true);
115                 }
116             };
117 
118     @Inject
KeyguardQsUserSwitchController( FrameLayout view, Context context, @Main Resources resources, ScreenLifecycle screenLifecycle, UserSwitcherController userSwitcherController, KeyguardStateController keyguardStateController, FalsingManager falsingManager, ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, DozeParameters dozeParameters, Provider<UserDetailView.Adapter> userDetailViewAdapterProvider, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, FeatureFlags featureFlags, UserSwitchDialogController userSwitchDialogController)119     public KeyguardQsUserSwitchController(
120             FrameLayout view,
121             Context context,
122             @Main Resources resources,
123             ScreenLifecycle screenLifecycle,
124             UserSwitcherController userSwitcherController,
125             KeyguardStateController keyguardStateController,
126             FalsingManager falsingManager,
127             ConfigurationController configurationController,
128             SysuiStatusBarStateController statusBarStateController,
129             DozeParameters dozeParameters,
130             Provider<UserDetailView.Adapter> userDetailViewAdapterProvider,
131             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
132             FeatureFlags featureFlags,
133             UserSwitchDialogController userSwitchDialogController) {
134         super(view);
135         if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
136         mContext = context;
137         mResources = resources;
138         mScreenLifecycle = screenLifecycle;
139         mUserSwitcherController = userSwitcherController;
140         mKeyguardStateController = keyguardStateController;
141         mFalsingManager = falsingManager;
142         mConfigurationController = configurationController;
143         mStatusBarStateController = statusBarStateController;
144         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
145                 keyguardStateController, dozeParameters,
146                 unlockedScreenOffAnimationController,  /* animateYPos= */ false);
147         mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider);
148         mFeatureFlags = featureFlags;
149         mUserSwitchDialogController = userSwitchDialogController;
150     }
151 
152     @Override
onInit()153     protected void onInit() {
154         super.onInit();
155         if (DEBUG) Log.d(TAG, "onInit");
156         mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
157         mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
158             @Override
159             public View getView(int position, View convertView, ViewGroup parent) {
160                 return null;
161             }
162         };
163 
164         mUserAvatarView.setOnClickListener(v -> {
165             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
166                 return;
167             }
168             if (isListAnimating()) {
169                 return;
170             }
171 
172             // Tapping anywhere in the view will open QS user panel
173             if (mFeatureFlags.useNewUserSwitcher()) {
174                 mUserSwitchDialogController.showDialog(mView);
175             } else {
176                 openQsUserPanel();
177             }
178         });
179 
180         mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
181             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
182                 super.onInitializeAccessibilityNodeInfo(host, info);
183                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
184                         AccessibilityNodeInfo.ACTION_CLICK,
185                         mContext.getString(
186                                 R.string.accessibility_quick_settings_choose_user_action)));
187             }
188         });
189     }
190 
191     @Override
onViewAttached()192     protected void onViewAttached() {
193         if (DEBUG) Log.d(TAG, "onViewAttached");
194         mAdapter.registerDataSetObserver(mDataSetObserver);
195         mDataSetObserver.onChanged();
196         mStatusBarStateController.addCallback(mStatusBarStateListener);
197         mConfigurationController.addCallback(mConfigurationListener);
198         updateView(true /* forceUpdate */);
199     }
200 
201     @Override
onViewDetached()202     protected void onViewDetached() {
203         if (DEBUG) Log.d(TAG, "onViewDetached");
204 
205         mAdapter.unregisterDataSetObserver(mDataSetObserver);
206         mStatusBarStateController.removeCallback(mStatusBarStateListener);
207         mConfigurationController.removeCallback(mConfigurationListener);
208     }
209 
210     public final DataSetObserver mDataSetObserver = new DataSetObserver() {
211         @Override
212         public void onChanged() {
213             updateView(false /* forceUpdate */);
214         }
215     };
216 
217     /**
218      * @return true if the current user has changed
219      */
updateCurrentUser()220     private boolean updateCurrentUser() {
221         UserSwitcherController.UserRecord previousUser = mCurrentUser;
222         mCurrentUser = null;
223         for (int i = 0; i < mAdapter.getCount(); i++) {
224             UserSwitcherController.UserRecord r = mAdapter.getItem(i);
225             if (r.isCurrent) {
226                 mCurrentUser = r;
227                 return !mCurrentUser.equals(previousUser);
228             }
229         }
230         return mCurrentUser == null && previousUser != null;
231     }
232 
233     /**
234      * @param forceUpdate whether to update view even if current user did not change
235      */
updateView(boolean forceUpdate)236     private void updateView(boolean forceUpdate) {
237         if (!updateCurrentUser() && !forceUpdate) {
238             return;
239         }
240 
241         String contentDescription = null;
242         if (mCurrentUser != null && mCurrentUser.info != null && !TextUtils.isEmpty(
243                 mCurrentUser.info.name)) {
244             // If we know the current user's name, have TalkBack to announce "Signed in as [user
245             // name]" when the icon is selected
246             contentDescription = mContext.getString(R.string.accessibility_quick_settings_user,
247                     mCurrentUser.info.name);
248         } else {
249             // As a fallback, have TalkBack announce "Switch user"
250             contentDescription = mContext.getString(
251                     R.string.accessibility_multi_user_switch_switcher);
252         }
253 
254         if (!TextUtils.equals(mUserAvatarView.getContentDescription(), contentDescription)) {
255             mUserAvatarView.setContentDescription(contentDescription);
256         }
257 
258         int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
259         mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
260     }
261 
getCurrentUserIcon()262     Drawable getCurrentUserIcon() {
263         Drawable drawable;
264         if (mCurrentUser == null || mCurrentUser.picture == null) {
265             if (mCurrentUser != null && mCurrentUser.isGuest) {
266                 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
267             } else {
268                 drawable = mContext.getDrawable(R.drawable.ic_avatar_user);
269             }
270             int iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
271             drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
272         } else {
273             int avatarSize = (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
274             drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
275         }
276 
277         Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
278         drawable = new LayerDrawable(new Drawable[]{bg, drawable});
279         return drawable;
280     }
281 
282     /**
283      * Get the height of the keyguard user switcher view when closed.
284      */
getUserIconHeight()285     public int getUserIconHeight() {
286         return mUserAvatarView.getHeight();
287     }
288 
289     /**
290      * Set the visibility of the user avatar view based on some new state.
291      */
setKeyguardQsUserSwitchVisibility( int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState)292     public void setKeyguardQsUserSwitchVisibility(
293             int statusBarState,
294             boolean keyguardFadingAway,
295             boolean goingToFullShade,
296             int oldStatusBarState) {
297         mKeyguardVisibilityHelper.setViewVisibility(
298                 statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
299     }
300 
301     /**
302      * Update position of the view with an optional animation
303      */
updatePosition(int x, int y, boolean animate)304     public void updatePosition(int x, int y, boolean animate) {
305         PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate);
306         PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
307                 ANIMATION_PROPERTIES, animate);
308     }
309 
310     /**
311      * Set keyguard user avatar view alpha.
312      */
setAlpha(float alpha)313     public void setAlpha(float alpha) {
314         if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
315             mView.setAlpha(alpha);
316         }
317     }
318 
isListAnimating()319     private boolean isListAnimating() {
320         return mKeyguardVisibilityHelper.isVisibilityAnimating();
321     }
322 
openQsUserPanel()323     private void openQsUserPanel() {
324         mNotificationPanelViewController.expandWithQsDetail(mUserDetailAdapter);
325     }
326 
setNotificationPanelViewController( NotificationPanelViewController notificationPanelViewController)327     public void setNotificationPanelViewController(
328             NotificationPanelViewController notificationPanelViewController) {
329         mNotificationPanelViewController = notificationPanelViewController;
330     }
331 
332     class KeyguardUserDetailAdapter extends UserSwitcherController.UserDetailAdapter {
KeyguardUserDetailAdapter(Context context, Provider<UserDetailView.Adapter> userDetailViewAdapterProvider)333         KeyguardUserDetailAdapter(Context context,
334                 Provider<UserDetailView.Adapter> userDetailViewAdapterProvider) {
335             super(context, userDetailViewAdapterProvider);
336         }
337 
338         @Override
shouldAnimate()339         public boolean shouldAnimate() {
340             return false;
341         }
342 
343         @Override
getDoneText()344         public int getDoneText() {
345             return R.string.quick_settings_close_user_panel;
346         }
347 
348         @Override
onDoneButtonClicked()349         public boolean onDoneButtonClicked() {
350             if (mNotificationPanelViewController != null) {
351                 mNotificationPanelViewController.animateCloseQs(true /* animateAway */);
352                 return true;
353             } else {
354                 return false;
355             }
356         }
357     }
358 }
359