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