• 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 static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA;
20 import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA;
21 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.ObjectAnimator;
25 import android.content.Context;
26 import android.content.res.Resources;
27 import android.database.DataSetObserver;
28 import android.graphics.Rect;
29 import android.graphics.drawable.Drawable;
30 import android.graphics.drawable.LayerDrawable;
31 import android.os.UserHandle;
32 import android.util.Log;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 
37 import com.android.keyguard.KeyguardConstants;
38 import com.android.keyguard.KeyguardUpdateMonitor;
39 import com.android.keyguard.KeyguardUpdateMonitorCallback;
40 import com.android.keyguard.KeyguardVisibilityHelper;
41 import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
42 import com.android.settingslib.drawable.CircleFramedDrawable;
43 import com.android.systemui.R;
44 import com.android.systemui.animation.Interpolators;
45 import com.android.systemui.dagger.qualifiers.Main;
46 import com.android.systemui.keyguard.ScreenLifecycle;
47 import com.android.systemui.plugins.statusbar.StatusBarStateController;
48 import com.android.systemui.statusbar.SysuiStatusBarStateController;
49 import com.android.systemui.statusbar.notification.AnimatableProperty;
50 import com.android.systemui.statusbar.notification.PropertyAnimator;
51 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
52 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
53 import com.android.systemui.statusbar.phone.DozeParameters;
54 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
55 import com.android.systemui.util.ViewController;
56 
57 import java.util.ArrayList;
58 
59 import javax.inject.Inject;
60 
61 /**
62  * Manages the user switcher on the Keyguard.
63  */
64 @KeyguardUserSwitcherScope
65 public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
66 
67     private static final String TAG = "KeyguardUserSwitcherController";
68     private static final boolean DEBUG = KeyguardConstants.DEBUG;
69 
70     private static final AnimationProperties ANIMATION_PROPERTIES =
71             new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
72 
73     private final Context mContext;
74     private final UserSwitcherController mUserSwitcherController;
75     private final ScreenLifecycle mScreenLifecycle;
76     private final KeyguardUserAdapter mAdapter;
77     private final KeyguardStateController mKeyguardStateController;
78     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
79     protected final SysuiStatusBarStateController mStatusBarStateController;
80     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
81     private ObjectAnimator mBgAnimator;
82     private final KeyguardUserSwitcherScrim mBackground;
83 
84     // Child views of KeyguardUserSwitcherView
85     private KeyguardUserSwitcherListView mListView;
86 
87     // State info for the user switcher
88     private boolean mUserSwitcherOpen;
89     private int mCurrentUserId = UserHandle.USER_NULL;
90     private int mBarState;
91     private float mDarkAmount;
92 
93     private final KeyguardUpdateMonitorCallback mInfoCallback =
94             new KeyguardUpdateMonitorCallback() {
95                 @Override
96                 public void onKeyguardVisibilityChanged(boolean showing) {
97                     if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing));
98                     // Any time the keyguard is hidden, try to close the user switcher menu to
99                     // restore keyguard to the default state
100                     if (!showing) {
101                         closeSwitcherIfOpenAndNotSimple(false);
102                     }
103                 }
104 
105                 @Override
106                 public void onUserSwitching(int userId) {
107                     closeSwitcherIfOpenAndNotSimple(false);
108                 }
109             };
110 
111     private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
112         @Override
113         public void onScreenTurnedOff() {
114             if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
115             closeSwitcherIfOpenAndNotSimple(false);
116         }
117     };
118 
119     private final StatusBarStateController.StateListener mStatusBarStateListener =
120             new StatusBarStateController.StateListener() {
121                 @Override
122                 public void onStateChanged(int newState) {
123                     if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState));
124 
125                     boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
126                     boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
127                     int oldState = mBarState;
128                     mBarState = newState;
129 
130                     if (mStatusBarStateController.goingToFullShade()
131                             || mKeyguardStateController.isKeyguardFadingAway()) {
132                         closeSwitcherIfOpenAndNotSimple(true);
133                     }
134 
135                     setKeyguardUserSwitcherVisibility(
136                             newState,
137                             keyguardFadingAway,
138                             goingToFullShade,
139                             oldState);
140                 }
141 
142                 @Override
143                 public void onDozeAmountChanged(float linearAmount, float amount) {
144                     if (DEBUG) {
145                         Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f",
146                                 linearAmount, amount));
147                     }
148                     setDarkAmount(amount);
149                 }
150             };
151 
152     @Inject
KeyguardUserSwitcherController( KeyguardUserSwitcherView keyguardUserSwitcherView, Context context, @Main Resources resources, LayoutInflater layoutInflater, ScreenLifecycle screenLifecycle, UserSwitcherController userSwitcherController, KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, DozeParameters dozeParameters, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController)153     public KeyguardUserSwitcherController(
154             KeyguardUserSwitcherView keyguardUserSwitcherView,
155             Context context,
156             @Main Resources resources,
157             LayoutInflater layoutInflater,
158             ScreenLifecycle screenLifecycle,
159             UserSwitcherController userSwitcherController,
160             KeyguardStateController keyguardStateController,
161             SysuiStatusBarStateController statusBarStateController,
162             KeyguardUpdateMonitor keyguardUpdateMonitor,
163             DozeParameters dozeParameters,
164             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
165         super(keyguardUserSwitcherView);
166         if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController");
167         mContext = context;
168         mScreenLifecycle = screenLifecycle;
169         mUserSwitcherController = userSwitcherController;
170         mKeyguardStateController = keyguardStateController;
171         mStatusBarStateController = statusBarStateController;
172         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
173         mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater,
174                 mUserSwitcherController, this);
175         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
176                 keyguardStateController, dozeParameters,
177                 unlockedScreenOffAnimationController, /* animateYPos= */ false);
178         mBackground = new KeyguardUserSwitcherScrim(context);
179     }
180 
181     @Override
onInit()182     protected void onInit() {
183         super.onInit();
184 
185         if (DEBUG) Log.d(TAG, "onInit");
186 
187         mListView = mView.findViewById(R.id.keyguard_user_switcher_list);
188 
189         mView.setOnTouchListener((v, event) -> {
190             if (!isListAnimating()) {
191                 // Hide switcher if it didn't handle the touch event (and block the event from
192                 // going through).
193                 return closeSwitcherIfOpenAndNotSimple(true);
194             }
195             return false;
196         });
197     }
198 
199     @Override
onViewAttached()200     protected void onViewAttached() {
201         if (DEBUG) Log.d(TAG, "onViewAttached");
202         mAdapter.registerDataSetObserver(mDataSetObserver);
203         mAdapter.notifyDataSetChanged();
204         mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
205         mStatusBarStateController.addCallback(mStatusBarStateListener);
206         mScreenLifecycle.addObserver(mScreenObserver);
207         if (isSimpleUserSwitcher()) {
208             // Don't use the background for the simple user switcher
209             setUserSwitcherOpened(true /* open */, true /* animate */);
210         } else {
211             mView.addOnLayoutChangeListener(mBackground);
212             mView.setBackground(mBackground);
213             mBackground.setAlpha(0);
214         }
215     }
216 
217     @Override
onViewDetached()218     protected void onViewDetached() {
219         if (DEBUG) Log.d(TAG, "onViewDetached");
220 
221         // Detaching the view will always close the switcher
222         closeSwitcherIfOpenAndNotSimple(false);
223 
224         mAdapter.unregisterDataSetObserver(mDataSetObserver);
225         mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
226         mStatusBarStateController.removeCallback(mStatusBarStateListener);
227         mScreenLifecycle.removeObserver(mScreenObserver);
228         mView.removeOnLayoutChangeListener(mBackground);
229         mView.setBackground(null);
230         mBackground.setAlpha(0);
231     }
232 
233     /**
234      * See:
235      *
236      * <ul>
237      *   <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li>
238      *    <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li>
239      * </ul>
240      *
241      * @return true if the user switcher should be open by default on the lock screen.
242      * @see android.os.UserManager#isUserSwitcherEnabled()
243      */
isSimpleUserSwitcher()244     public boolean isSimpleUserSwitcher() {
245         return mUserSwitcherController.isSimpleUserSwitcher();
246     }
247 
248     /**
249      * @param animate if the transition should be animated
250      * @return true if the switcher state changed
251      */
closeSwitcherIfOpenAndNotSimple(boolean animate)252     public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) {
253         if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) {
254             setUserSwitcherOpened(false /* open */, animate);
255             return true;
256         }
257         return false;
258     }
259 
260     public final DataSetObserver mDataSetObserver = new DataSetObserver() {
261         @Override
262         public void onChanged() {
263             refreshUserList();
264         }
265     };
266 
refreshUserList()267     void refreshUserList() {
268         final int childCount = mListView.getChildCount();
269         final int adapterCount = mAdapter.getCount();
270         final int count = Math.max(childCount, adapterCount);
271 
272         if (DEBUG) {
273             Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount,
274                     adapterCount));
275         }
276 
277         boolean foundCurrentUser = false;
278         for (int i = 0; i < count; i++) {
279             if (i < adapterCount) {
280                 View oldView = null;
281                 if (i < childCount) {
282                     oldView = mListView.getChildAt(i);
283                 }
284                 KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView)
285                         mAdapter.getView(i, oldView, mListView);
286                 UserSwitcherController.UserRecord userTag =
287                         (UserSwitcherController.UserRecord) newView.getTag();
288                 if (userTag.isCurrent) {
289                     if (i != 0) {
290                         Log.w(TAG, "Current user is not the first view in the list");
291                     }
292                     foundCurrentUser = true;
293                     mCurrentUserId = userTag.info.id;
294                     // Current user is always visible
295                     newView.updateVisibilities(true /* showItem */,
296                             mUserSwitcherOpen /* showTextName */, false /* animate */);
297                 } else {
298                     // Views for non-current users are always expanded (e.g. they should the name
299                     // next to the user icon). However, they could be hidden entirely if the list
300                     // is closed.
301                     newView.updateVisibilities(mUserSwitcherOpen /* showItem */,
302                             true /* showTextName */, false /* animate */);
303                 }
304                 newView.setDarkAmount(mDarkAmount);
305                 if (oldView == null) {
306                     // We ran out of existing views. Add it at the end.
307                     mListView.addView(newView);
308                 } else if (oldView != newView) {
309                     // We couldn't rebind the view. Replace it.
310                     mListView.replaceView(newView, i);
311                 }
312             } else {
313                 mListView.removeLastView();
314             }
315         }
316         if (!foundCurrentUser) {
317             Log.w(TAG, "Current user is not listed");
318             mCurrentUserId = UserHandle.USER_NULL;
319         }
320     }
321 
322     /**
323      * Set the visibility of the keyguard user switcher view based on some new state.
324      */
setKeyguardUserSwitcherVisibility( int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState)325     public void setKeyguardUserSwitcherVisibility(
326             int statusBarState,
327             boolean keyguardFadingAway,
328             boolean goingToFullShade,
329             int oldStatusBarState) {
330         mKeyguardVisibilityHelper.setViewVisibility(
331                 statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
332     }
333 
334     /**
335      * Update position of the view with an optional animation
336      */
updatePosition(int x, int y, boolean animate)337     public void updatePosition(int x, int y, boolean animate) {
338         PropertyAnimator.setProperty(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES,
339                 animate);
340         PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
341                 ANIMATION_PROPERTIES, animate);
342 
343         Rect r = new Rect();
344         mListView.getDrawingRect(r);
345         mView.offsetDescendantRectToMyCoords(mListView, r);
346         mBackground.setGradientCenter(
347                 (int) (mListView.getTranslationX() + r.left + r.width() / 2),
348                 (int) (mListView.getTranslationY() + r.top + r.height() / 2));
349     }
350 
351     /**
352      * Set keyguard user switcher view alpha.
353      */
setAlpha(float alpha)354     public void setAlpha(float alpha) {
355         if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
356             mView.setAlpha(alpha);
357         }
358     }
359 
360     /**
361      * Set the amount (ratio) that the device has transitioned to doze.
362      *
363      * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
364      */
setDarkAmount(float darkAmount)365     private void setDarkAmount(float darkAmount) {
366         boolean isFullyDozed = darkAmount == 1;
367         if (darkAmount == mDarkAmount) {
368             return;
369         }
370         mDarkAmount = darkAmount;
371         mListView.setDarkAmount(darkAmount);
372         if (isFullyDozed) {
373             closeSwitcherIfOpenAndNotSimple(false);
374         }
375     }
376 
isListAnimating()377     private boolean isListAnimating() {
378         return mKeyguardVisibilityHelper.isVisibilityAnimating() || mListView.isAnimating();
379     }
380 
381     /**
382      * NOTE: switcher state is updated before animations finish.
383      *
384      * @param animate true to animate transition. The user switcher state (i.e.
385      *                {@link #isUserSwitcherOpen()}) is updated before animation is finished.
386      */
setUserSwitcherOpened(boolean open, boolean animate)387     private void setUserSwitcherOpened(boolean open, boolean animate) {
388         if (DEBUG) {
389             Log.d(TAG,
390                     String.format("setUserSwitcherOpened: %b -> %b (animate=%b)",
391                             mUserSwitcherOpen, open, animate));
392         }
393         mUserSwitcherOpen = open;
394         updateVisibilities(animate);
395     }
396 
updateVisibilities(boolean animate)397     private void updateVisibilities(boolean animate) {
398         if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate));
399         if (mBgAnimator != null) {
400             mBgAnimator.cancel();
401         }
402 
403         if (mUserSwitcherOpen) {
404             mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
405             mBgAnimator.setDuration(400);
406             mBgAnimator.setInterpolator(Interpolators.ALPHA_IN);
407             mBgAnimator.addListener(new AnimatorListenerAdapter() {
408                 @Override
409                 public void onAnimationEnd(Animator animation) {
410                     mBgAnimator = null;
411                 }
412             });
413             mBgAnimator.start();
414         } else {
415             mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 255, 0);
416             mBgAnimator.setDuration(400);
417             mBgAnimator.setInterpolator(Interpolators.ALPHA_OUT);
418             mBgAnimator.addListener(new AnimatorListenerAdapter() {
419                 @Override
420                 public void onAnimationEnd(Animator animation) {
421                     mBgAnimator = null;
422                 }
423             });
424             mBgAnimator.start();
425         }
426         mListView.updateVisibilities(mUserSwitcherOpen, animate);
427     }
428 
isUserSwitcherOpen()429     private boolean isUserSwitcherOpen() {
430         return mUserSwitcherOpen;
431     }
432 
433     static class KeyguardUserAdapter extends
434             UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
435 
436         private final Context mContext;
437         private final Resources mResources;
438         private final LayoutInflater mLayoutInflater;
439         private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
440         private View mCurrentUserView;
441         // List of users where the first entry is always the current user
442         private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
443 
KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater, UserSwitcherController controller, KeyguardUserSwitcherController keyguardUserSwitcherController)444         KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater,
445                 UserSwitcherController controller,
446                 KeyguardUserSwitcherController keyguardUserSwitcherController) {
447             super(controller);
448             mContext = context;
449             mResources = resources;
450             mLayoutInflater = layoutInflater;
451             mKeyguardUserSwitcherController = keyguardUserSwitcherController;
452         }
453 
454         @Override
notifyDataSetChanged()455         public void notifyDataSetChanged() {
456             // At this point, value of isSimpleUserSwitcher() may have changed in addition to the
457             // data set
458             refreshUserOrder();
459             super.notifyDataSetChanged();
460         }
461 
refreshUserOrder()462         void refreshUserOrder() {
463             ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
464             mUsersOrdered = new ArrayList<>(users.size());
465             for (int i = 0; i < users.size(); i++) {
466                 UserSwitcherController.UserRecord record = users.get(i);
467                 if (record.isCurrent) {
468                     mUsersOrdered.add(0, record);
469                 } else {
470                     mUsersOrdered.add(record);
471                 }
472             }
473         }
474 
475         @Override
getUsers()476         protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
477             return mUsersOrdered;
478         }
479 
480         @Override
getView(int position, View convertView, ViewGroup parent)481         public View getView(int position, View convertView, ViewGroup parent) {
482             UserSwitcherController.UserRecord item = getItem(position);
483             return createUserDetailItemView(convertView, parent, item);
484         }
485 
convertOrInflate(View convertView, ViewGroup parent)486         KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
487             if (!(convertView instanceof KeyguardUserDetailItemView)
488                     || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
489                 convertView = mLayoutInflater.inflate(
490                         R.layout.keyguard_user_switcher_item, parent, false);
491             }
492             return (KeyguardUserDetailItemView) convertView;
493         }
494 
createUserDetailItemView(View convertView, ViewGroup parent, UserSwitcherController.UserRecord item)495         KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
496                 UserSwitcherController.UserRecord item) {
497             KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
498             v.setOnClickListener(this);
499 
500             String name = getName(mContext, item);
501             if (item.picture == null) {
502                 v.bind(name, getDrawable(item).mutate(), item.resolveId());
503             } else {
504                 int avatarSize =
505                         (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
506                 Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
507                 drawable.setColorFilter(
508                         item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
509                 v.bind(name, drawable, item.info.id);
510             }
511             v.setActivated(item.isCurrent);
512             v.setDisabledByAdmin(item.isDisabledByAdmin);
513             v.setEnabled(item.isSwitchToEnabled);
514             v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
515 
516             if (item.isCurrent) {
517                 mCurrentUserView = v;
518             }
519             v.setTag(item);
520             return v;
521         }
522 
getDrawable(UserSwitcherController.UserRecord item)523         private Drawable getDrawable(UserSwitcherController.UserRecord item) {
524             Drawable drawable;
525             if (item.isCurrent && item.isGuest) {
526                 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
527             } else {
528                 drawable = getIconDrawable(mContext, item);
529             }
530 
531             int iconColorRes;
532             if (item.isSwitchToEnabled) {
533                 iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
534             } else {
535                 iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
536             }
537             drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
538 
539             Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
540             drawable = new LayerDrawable(new Drawable[]{bg, drawable});
541             return drawable;
542         }
543 
544         @Override
onClick(View v)545         public void onClick(View v) {
546             UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
547 
548             if (mKeyguardUserSwitcherController.isListAnimating()) {
549                 return;
550             }
551 
552             if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) {
553                 if (!user.isCurrent || user.isGuest) {
554                     onUserListItemClicked(user);
555                 } else {
556                     mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
557                             true /* animate */);
558                 }
559             } else {
560                 // If switcher is closed, tapping anywhere in the view will open it
561                 mKeyguardUserSwitcherController.setUserSwitcherOpened(
562                         true /* open */, true /* animate */);
563             }
564         }
565     }
566 }
567