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