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