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