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 
getHeight()248     public int getHeight() {
249         return mListView.getHeight();
250     }
251 
252     /**
253      * @param animate if the transition should be animated
254      * @return true if the switcher state changed
255      */
closeSwitcherIfOpenAndNotSimple(boolean animate)256     public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) {
257         if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) {
258             setUserSwitcherOpened(false /* open */, animate);
259             return true;
260         }
261         return false;
262     }
263 
264     public final DataSetObserver mDataSetObserver = new DataSetObserver() {
265         @Override
266         public void onChanged() {
267             refreshUserList();
268         }
269     };
270 
refreshUserList()271     void refreshUserList() {
272         final int childCount = mListView.getChildCount();
273         final int adapterCount = mAdapter.getCount();
274         final int count = Math.max(childCount, adapterCount);
275 
276         if (DEBUG) {
277             Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount,
278                     adapterCount));
279         }
280 
281         boolean foundCurrentUser = false;
282         for (int i = 0; i < count; i++) {
283             if (i < adapterCount) {
284                 View oldView = null;
285                 if (i < childCount) {
286                     oldView = mListView.getChildAt(i);
287                 }
288                 KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView)
289                         mAdapter.getView(i, oldView, mListView);
290                 UserSwitcherController.UserRecord userTag =
291                         (UserSwitcherController.UserRecord) newView.getTag();
292                 if (userTag.isCurrent) {
293                     if (i != 0) {
294                         Log.w(TAG, "Current user is not the first view in the list");
295                     }
296                     foundCurrentUser = true;
297                     mCurrentUserId = userTag.info.id;
298                     // Current user is always visible
299                     newView.updateVisibilities(true /* showItem */,
300                             mUserSwitcherOpen /* showTextName */, false /* animate */);
301                 } else {
302                     // Views for non-current users are always expanded (e.g. they should the name
303                     // next to the user icon). However, they could be hidden entirely if the list
304                     // is closed.
305                     newView.updateVisibilities(mUserSwitcherOpen /* showItem */,
306                             true /* showTextName */, false /* animate */);
307                 }
308                 newView.setDarkAmount(mDarkAmount);
309                 if (oldView == null) {
310                     // We ran out of existing views. Add it at the end.
311                     mListView.addView(newView);
312                 } else if (oldView != newView) {
313                     // We couldn't rebind the view. Replace it.
314                     mListView.replaceView(newView, i);
315                 }
316             } else {
317                 mListView.removeLastView();
318             }
319         }
320         if (!foundCurrentUser) {
321             Log.w(TAG, "Current user is not listed");
322             mCurrentUserId = UserHandle.USER_NULL;
323         }
324     }
325 
326     /**
327      * Set the visibility of the keyguard user switcher view based on some new state.
328      */
setKeyguardUserSwitcherVisibility( int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState)329     public void setKeyguardUserSwitcherVisibility(
330             int statusBarState,
331             boolean keyguardFadingAway,
332             boolean goingToFullShade,
333             int oldStatusBarState) {
334         mKeyguardVisibilityHelper.setViewVisibility(
335                 statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
336     }
337 
338     /**
339      * Update position of the view with an optional animation
340      */
updatePosition(int x, int y, boolean animate)341     public void updatePosition(int x, int y, boolean animate) {
342         PropertyAnimator.setProperty(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES,
343                 animate);
344         PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
345                 ANIMATION_PROPERTIES, animate);
346 
347         Rect r = new Rect();
348         mListView.getDrawingRect(r);
349         mView.offsetDescendantRectToMyCoords(mListView, r);
350         mBackground.setGradientCenter(
351                 (int) (mListView.getTranslationX() + r.left + r.width() / 2),
352                 (int) (mListView.getTranslationY() + r.top + r.height() / 2));
353     }
354 
355     /**
356      * Set keyguard user switcher view alpha.
357      */
setAlpha(float alpha)358     public void setAlpha(float alpha) {
359         if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
360             mView.setAlpha(alpha);
361         }
362     }
363 
364     /**
365      * Set the amount (ratio) that the device has transitioned to doze.
366      *
367      * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
368      */
setDarkAmount(float darkAmount)369     private void setDarkAmount(float darkAmount) {
370         boolean isFullyDozed = darkAmount == 1;
371         if (darkAmount == mDarkAmount) {
372             return;
373         }
374         mDarkAmount = darkAmount;
375         mListView.setDarkAmount(darkAmount);
376         if (isFullyDozed) {
377             closeSwitcherIfOpenAndNotSimple(false);
378         }
379     }
380 
isListAnimating()381     private boolean isListAnimating() {
382         return mKeyguardVisibilityHelper.isVisibilityAnimating() || mListView.isAnimating();
383     }
384 
385     /**
386      * NOTE: switcher state is updated before animations finish.
387      *
388      * @param animate true to animate transition. The user switcher state (i.e.
389      *                {@link #isUserSwitcherOpen()}) is updated before animation is finished.
390      */
setUserSwitcherOpened(boolean open, boolean animate)391     private void setUserSwitcherOpened(boolean open, boolean animate) {
392         if (DEBUG) {
393             Log.d(TAG,
394                     String.format("setUserSwitcherOpened: %b -> %b (animate=%b)",
395                             mUserSwitcherOpen, open, animate));
396         }
397         mUserSwitcherOpen = open;
398         updateVisibilities(animate);
399     }
400 
updateVisibilities(boolean animate)401     private void updateVisibilities(boolean animate) {
402         if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate));
403         if (mBgAnimator != null) {
404             mBgAnimator.cancel();
405         }
406 
407         if (mUserSwitcherOpen) {
408             mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255);
409             mBgAnimator.setDuration(400);
410             mBgAnimator.setInterpolator(Interpolators.ALPHA_IN);
411             mBgAnimator.addListener(new AnimatorListenerAdapter() {
412                 @Override
413                 public void onAnimationEnd(Animator animation) {
414                     mBgAnimator = null;
415                 }
416             });
417             mBgAnimator.start();
418         } else {
419             mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 255, 0);
420             mBgAnimator.setDuration(400);
421             mBgAnimator.setInterpolator(Interpolators.ALPHA_OUT);
422             mBgAnimator.addListener(new AnimatorListenerAdapter() {
423                 @Override
424                 public void onAnimationEnd(Animator animation) {
425                     mBgAnimator = null;
426                 }
427             });
428             mBgAnimator.start();
429         }
430         mListView.updateVisibilities(mUserSwitcherOpen, animate);
431     }
432 
isUserSwitcherOpen()433     private boolean isUserSwitcherOpen() {
434         return mUserSwitcherOpen;
435     }
436 
437     static class KeyguardUserAdapter extends
438             UserSwitcherController.BaseUserAdapter implements View.OnClickListener {
439 
440         private final Context mContext;
441         private final Resources mResources;
442         private final LayoutInflater mLayoutInflater;
443         private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
444         private View mCurrentUserView;
445         // List of users where the first entry is always the current user
446         private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>();
447 
KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater, UserSwitcherController controller, KeyguardUserSwitcherController keyguardUserSwitcherController)448         KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater,
449                 UserSwitcherController controller,
450                 KeyguardUserSwitcherController keyguardUserSwitcherController) {
451             super(controller);
452             mContext = context;
453             mResources = resources;
454             mLayoutInflater = layoutInflater;
455             mKeyguardUserSwitcherController = keyguardUserSwitcherController;
456         }
457 
458         @Override
notifyDataSetChanged()459         public void notifyDataSetChanged() {
460             // At this point, value of isSimpleUserSwitcher() may have changed in addition to the
461             // data set
462             refreshUserOrder();
463             super.notifyDataSetChanged();
464         }
465 
refreshUserOrder()466         void refreshUserOrder() {
467             ArrayList<UserSwitcherController.UserRecord> users = super.getUsers();
468             mUsersOrdered = new ArrayList<>(users.size());
469             for (int i = 0; i < users.size(); i++) {
470                 UserSwitcherController.UserRecord record = users.get(i);
471                 if (record.isCurrent) {
472                     mUsersOrdered.add(0, record);
473                 } else {
474                     mUsersOrdered.add(record);
475                 }
476             }
477         }
478 
479         @Override
getUsers()480         protected ArrayList<UserSwitcherController.UserRecord> getUsers() {
481             return mUsersOrdered;
482         }
483 
484         @Override
getView(int position, View convertView, ViewGroup parent)485         public View getView(int position, View convertView, ViewGroup parent) {
486             UserSwitcherController.UserRecord item = getItem(position);
487             return createUserDetailItemView(convertView, parent, item);
488         }
489 
convertOrInflate(View convertView, ViewGroup parent)490         KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) {
491             if (!(convertView instanceof KeyguardUserDetailItemView)
492                     || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) {
493                 convertView = mLayoutInflater.inflate(
494                         R.layout.keyguard_user_switcher_item, parent, false);
495             }
496             return (KeyguardUserDetailItemView) convertView;
497         }
498 
createUserDetailItemView(View convertView, ViewGroup parent, UserSwitcherController.UserRecord item)499         KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
500                 UserSwitcherController.UserRecord item) {
501             KeyguardUserDetailItemView v = convertOrInflate(convertView, parent);
502             v.setOnClickListener(this);
503 
504             String name = getName(mContext, item);
505             if (item.picture == null) {
506                 v.bind(name, getDrawable(item).mutate(), item.resolveId());
507             } else {
508                 int avatarSize =
509                         (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
510                 Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize);
511                 drawable.setColorFilter(
512                         item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter());
513                 v.bind(name, drawable, item.info.id);
514             }
515             v.setActivated(item.isCurrent);
516             v.setDisabledByAdmin(item.isDisabledByAdmin);
517             v.setEnabled(item.isSwitchToEnabled);
518             v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA);
519 
520             if (item.isCurrent) {
521                 mCurrentUserView = v;
522             }
523             v.setTag(item);
524             return v;
525         }
526 
getDrawable(UserSwitcherController.UserRecord item)527         private Drawable getDrawable(UserSwitcherController.UserRecord item) {
528             Drawable drawable;
529             if (item.isCurrent && item.isGuest) {
530                 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
531             } else {
532                 drawable = getIconDrawable(mContext, item);
533             }
534 
535             int iconColorRes;
536             if (item.isSwitchToEnabled) {
537                 iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
538             } else {
539                 iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
540             }
541             drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
542 
543             Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar);
544             drawable = new LayerDrawable(new Drawable[]{bg, drawable});
545             return drawable;
546         }
547 
548         @Override
onClick(View v)549         public void onClick(View v) {
550             UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag();
551 
552             if (mKeyguardUserSwitcherController.isListAnimating()) {
553                 return;
554             }
555 
556             if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) {
557                 if (!user.isCurrent || user.isGuest) {
558                     onUserListItemClicked(user);
559                 } else {
560                     mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(
561                             true /* animate */);
562                 }
563             } else {
564                 // If switcher is closed, tapping anywhere in the view will open it
565                 mKeyguardUserSwitcherController.setUserSwitcherOpened(
566                         true /* open */, true /* animate */);
567             }
568         }
569     }
570 }
571