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