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