1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.policy; 18 19 import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA; 20 import static com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.animation.ObjectAnimator; 25 import android.content.Context; 26 import android.content.res.Resources; 27 import android.database.DataSetObserver; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 import android.graphics.drawable.LayerDrawable; 31 import android.os.UserHandle; 32 import android.util.Log; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 36 37 import com.android.keyguard.KeyguardConstants; 38 import com.android.keyguard.KeyguardUpdateMonitor; 39 import com.android.keyguard.KeyguardUpdateMonitorCallback; 40 import com.android.keyguard.KeyguardVisibilityHelper; 41 import com.android.keyguard.dagger.KeyguardUserSwitcherScope; 42 import com.android.settingslib.drawable.CircleFramedDrawable; 43 import com.android.systemui.R; 44 import com.android.systemui.animation.Interpolators; 45 import com.android.systemui.dagger.qualifiers.Main; 46 import com.android.systemui.keyguard.ScreenLifecycle; 47 import com.android.systemui.plugins.statusbar.StatusBarStateController; 48 import com.android.systemui.statusbar.SysuiStatusBarStateController; 49 import com.android.systemui.statusbar.notification.AnimatableProperty; 50 import com.android.systemui.statusbar.notification.PropertyAnimator; 51 import com.android.systemui.statusbar.notification.stack.AnimationProperties; 52 import com.android.systemui.statusbar.notification.stack.StackStateAnimator; 53 import com.android.systemui.statusbar.phone.DozeParameters; 54 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; 55 import com.android.systemui.util.ViewController; 56 57 import java.util.ArrayList; 58 59 import javax.inject.Inject; 60 61 /** 62 * Manages the user switcher on the Keyguard. 63 */ 64 @KeyguardUserSwitcherScope 65 public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> { 66 67 private static final String TAG = "KeyguardUserSwitcherController"; 68 private static final boolean DEBUG = KeyguardConstants.DEBUG; 69 70 private static final AnimationProperties ANIMATION_PROPERTIES = 71 new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 72 73 private final Context mContext; 74 private final UserSwitcherController mUserSwitcherController; 75 private final ScreenLifecycle mScreenLifecycle; 76 private final KeyguardUserAdapter mAdapter; 77 private final KeyguardStateController mKeyguardStateController; 78 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 79 protected final SysuiStatusBarStateController mStatusBarStateController; 80 private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; 81 private ObjectAnimator mBgAnimator; 82 private final KeyguardUserSwitcherScrim mBackground; 83 84 // Child views of KeyguardUserSwitcherView 85 private KeyguardUserSwitcherListView mListView; 86 87 // State info for the user switcher 88 private boolean mUserSwitcherOpen; 89 private int mCurrentUserId = UserHandle.USER_NULL; 90 private int mBarState; 91 private float mDarkAmount; 92 93 private final KeyguardUpdateMonitorCallback mInfoCallback = 94 new KeyguardUpdateMonitorCallback() { 95 @Override 96 public void onKeyguardVisibilityChanged(boolean showing) { 97 if (DEBUG) Log.d(TAG, String.format("onKeyguardVisibilityChanged %b", showing)); 98 // Any time the keyguard is hidden, try to close the user switcher menu to 99 // restore keyguard to the default state 100 if (!showing) { 101 closeSwitcherIfOpenAndNotSimple(false); 102 } 103 } 104 105 @Override 106 public void onUserSwitching(int userId) { 107 closeSwitcherIfOpenAndNotSimple(false); 108 } 109 }; 110 111 private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { 112 @Override 113 public void onScreenTurnedOff() { 114 if (DEBUG) Log.d(TAG, "onScreenTurnedOff"); 115 closeSwitcherIfOpenAndNotSimple(false); 116 } 117 }; 118 119 private final StatusBarStateController.StateListener mStatusBarStateListener = 120 new StatusBarStateController.StateListener() { 121 @Override 122 public void onStateChanged(int newState) { 123 if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState)); 124 125 boolean goingToFullShade = mStatusBarStateController.goingToFullShade(); 126 boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway(); 127 int oldState = mBarState; 128 mBarState = newState; 129 130 if (mStatusBarStateController.goingToFullShade() 131 || mKeyguardStateController.isKeyguardFadingAway()) { 132 closeSwitcherIfOpenAndNotSimple(true); 133 } 134 135 setKeyguardUserSwitcherVisibility( 136 newState, 137 keyguardFadingAway, 138 goingToFullShade, 139 oldState); 140 } 141 142 @Override 143 public void onDozeAmountChanged(float linearAmount, float amount) { 144 if (DEBUG) { 145 Log.d(TAG, String.format("onDozeAmountChanged: linearAmount=%f amount=%f", 146 linearAmount, amount)); 147 } 148 setDarkAmount(amount); 149 } 150 }; 151 152 @Inject KeyguardUserSwitcherController( KeyguardUserSwitcherView keyguardUserSwitcherView, Context context, @Main Resources resources, LayoutInflater layoutInflater, ScreenLifecycle screenLifecycle, UserSwitcherController userSwitcherController, KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, DozeParameters dozeParameters, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController)153 public KeyguardUserSwitcherController( 154 KeyguardUserSwitcherView keyguardUserSwitcherView, 155 Context context, 156 @Main Resources resources, 157 LayoutInflater layoutInflater, 158 ScreenLifecycle screenLifecycle, 159 UserSwitcherController userSwitcherController, 160 KeyguardStateController keyguardStateController, 161 SysuiStatusBarStateController statusBarStateController, 162 KeyguardUpdateMonitor keyguardUpdateMonitor, 163 DozeParameters dozeParameters, 164 UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { 165 super(keyguardUserSwitcherView); 166 if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController"); 167 mContext = context; 168 mScreenLifecycle = screenLifecycle; 169 mUserSwitcherController = userSwitcherController; 170 mKeyguardStateController = keyguardStateController; 171 mStatusBarStateController = statusBarStateController; 172 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 173 mAdapter = new KeyguardUserAdapter(mContext, resources, layoutInflater, 174 mUserSwitcherController, this); 175 mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, 176 keyguardStateController, dozeParameters, 177 unlockedScreenOffAnimationController, /* animateYPos= */ false); 178 mBackground = new KeyguardUserSwitcherScrim(context); 179 } 180 181 @Override onInit()182 protected void onInit() { 183 super.onInit(); 184 185 if (DEBUG) Log.d(TAG, "onInit"); 186 187 mListView = mView.findViewById(R.id.keyguard_user_switcher_list); 188 189 mView.setOnTouchListener((v, event) -> { 190 if (!isListAnimating()) { 191 // Hide switcher if it didn't handle the touch event (and block the event from 192 // going through). 193 return closeSwitcherIfOpenAndNotSimple(true); 194 } 195 return false; 196 }); 197 } 198 199 @Override onViewAttached()200 protected void onViewAttached() { 201 if (DEBUG) Log.d(TAG, "onViewAttached"); 202 mAdapter.registerDataSetObserver(mDataSetObserver); 203 mAdapter.notifyDataSetChanged(); 204 mKeyguardUpdateMonitor.registerCallback(mInfoCallback); 205 mStatusBarStateController.addCallback(mStatusBarStateListener); 206 mScreenLifecycle.addObserver(mScreenObserver); 207 if (isSimpleUserSwitcher()) { 208 // Don't use the background for the simple user switcher 209 setUserSwitcherOpened(true /* open */, true /* animate */); 210 } else { 211 mView.addOnLayoutChangeListener(mBackground); 212 mView.setBackground(mBackground); 213 mBackground.setAlpha(0); 214 } 215 } 216 217 @Override onViewDetached()218 protected void onViewDetached() { 219 if (DEBUG) Log.d(TAG, "onViewDetached"); 220 221 // Detaching the view will always close the switcher 222 closeSwitcherIfOpenAndNotSimple(false); 223 224 mAdapter.unregisterDataSetObserver(mDataSetObserver); 225 mKeyguardUpdateMonitor.removeCallback(mInfoCallback); 226 mStatusBarStateController.removeCallback(mStatusBarStateListener); 227 mScreenLifecycle.removeObserver(mScreenObserver); 228 mView.removeOnLayoutChangeListener(mBackground); 229 mView.setBackground(null); 230 mBackground.setAlpha(0); 231 } 232 233 /** 234 * See: 235 * 236 * <ul> 237 * <li>{@link com.android.internal.R.bool.config_expandLockScreenUserSwitcher}</li> 238 * <li>{@link UserSwitcherController.SIMPLE_USER_SWITCHER_GLOBAL_SETTING}</li> 239 * </ul> 240 * 241 * @return true if the user switcher should be open by default on the lock screen. 242 * @see android.os.UserManager#isUserSwitcherEnabled() 243 */ isSimpleUserSwitcher()244 public boolean isSimpleUserSwitcher() { 245 return mUserSwitcherController.isSimpleUserSwitcher(); 246 } 247 getHeight()248 public int getHeight() { 249 return mListView.getHeight(); 250 } 251 252 /** 253 * @param animate if the transition should be animated 254 * @return true if the switcher state changed 255 */ closeSwitcherIfOpenAndNotSimple(boolean animate)256 public boolean closeSwitcherIfOpenAndNotSimple(boolean animate) { 257 if (isUserSwitcherOpen() && !isSimpleUserSwitcher()) { 258 setUserSwitcherOpened(false /* open */, animate); 259 return true; 260 } 261 return false; 262 } 263 264 public final DataSetObserver mDataSetObserver = new DataSetObserver() { 265 @Override 266 public void onChanged() { 267 refreshUserList(); 268 } 269 }; 270 refreshUserList()271 void refreshUserList() { 272 final int childCount = mListView.getChildCount(); 273 final int adapterCount = mAdapter.getCount(); 274 final int count = Math.max(childCount, adapterCount); 275 276 if (DEBUG) { 277 Log.d(TAG, String.format("refreshUserList childCount=%d adapterCount=%d", childCount, 278 adapterCount)); 279 } 280 281 boolean foundCurrentUser = false; 282 for (int i = 0; i < count; i++) { 283 if (i < adapterCount) { 284 View oldView = null; 285 if (i < childCount) { 286 oldView = mListView.getChildAt(i); 287 } 288 KeyguardUserDetailItemView newView = (KeyguardUserDetailItemView) 289 mAdapter.getView(i, oldView, mListView); 290 UserSwitcherController.UserRecord userTag = 291 (UserSwitcherController.UserRecord) newView.getTag(); 292 if (userTag.isCurrent) { 293 if (i != 0) { 294 Log.w(TAG, "Current user is not the first view in the list"); 295 } 296 foundCurrentUser = true; 297 mCurrentUserId = userTag.info.id; 298 // Current user is always visible 299 newView.updateVisibilities(true /* showItem */, 300 mUserSwitcherOpen /* showTextName */, false /* animate */); 301 } else { 302 // Views for non-current users are always expanded (e.g. they should the name 303 // next to the user icon). However, they could be hidden entirely if the list 304 // is closed. 305 newView.updateVisibilities(mUserSwitcherOpen /* showItem */, 306 true /* showTextName */, false /* animate */); 307 } 308 newView.setDarkAmount(mDarkAmount); 309 if (oldView == null) { 310 // We ran out of existing views. Add it at the end. 311 mListView.addView(newView); 312 } else if (oldView != newView) { 313 // We couldn't rebind the view. Replace it. 314 mListView.replaceView(newView, i); 315 } 316 } else { 317 mListView.removeLastView(); 318 } 319 } 320 if (!foundCurrentUser) { 321 Log.w(TAG, "Current user is not listed"); 322 mCurrentUserId = UserHandle.USER_NULL; 323 } 324 } 325 326 /** 327 * Set the visibility of the keyguard user switcher view based on some new state. 328 */ setKeyguardUserSwitcherVisibility( int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState)329 public void setKeyguardUserSwitcherVisibility( 330 int statusBarState, 331 boolean keyguardFadingAway, 332 boolean goingToFullShade, 333 int oldStatusBarState) { 334 mKeyguardVisibilityHelper.setViewVisibility( 335 statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState); 336 } 337 338 /** 339 * Update position of the view with an optional animation 340 */ updatePosition(int x, int y, boolean animate)341 public void updatePosition(int x, int y, boolean animate) { 342 PropertyAnimator.setProperty(mListView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, 343 animate); 344 PropertyAnimator.setProperty(mListView, AnimatableProperty.TRANSLATION_X, -Math.abs(x), 345 ANIMATION_PROPERTIES, animate); 346 347 Rect r = new Rect(); 348 mListView.getDrawingRect(r); 349 mView.offsetDescendantRectToMyCoords(mListView, r); 350 mBackground.setGradientCenter( 351 (int) (mListView.getTranslationX() + r.left + r.width() / 2), 352 (int) (mListView.getTranslationY() + r.top + r.height() / 2)); 353 } 354 355 /** 356 * Set keyguard user switcher view alpha. 357 */ setAlpha(float alpha)358 public void setAlpha(float alpha) { 359 if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { 360 mView.setAlpha(alpha); 361 } 362 } 363 364 /** 365 * Set the amount (ratio) that the device has transitioned to doze. 366 * 367 * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. 368 */ setDarkAmount(float darkAmount)369 private void setDarkAmount(float darkAmount) { 370 boolean isFullyDozed = darkAmount == 1; 371 if (darkAmount == mDarkAmount) { 372 return; 373 } 374 mDarkAmount = darkAmount; 375 mListView.setDarkAmount(darkAmount); 376 if (isFullyDozed) { 377 closeSwitcherIfOpenAndNotSimple(false); 378 } 379 } 380 isListAnimating()381 private boolean isListAnimating() { 382 return mKeyguardVisibilityHelper.isVisibilityAnimating() || mListView.isAnimating(); 383 } 384 385 /** 386 * NOTE: switcher state is updated before animations finish. 387 * 388 * @param animate true to animate transition. The user switcher state (i.e. 389 * {@link #isUserSwitcherOpen()}) is updated before animation is finished. 390 */ setUserSwitcherOpened(boolean open, boolean animate)391 private void setUserSwitcherOpened(boolean open, boolean animate) { 392 if (DEBUG) { 393 Log.d(TAG, 394 String.format("setUserSwitcherOpened: %b -> %b (animate=%b)", 395 mUserSwitcherOpen, open, animate)); 396 } 397 mUserSwitcherOpen = open; 398 updateVisibilities(animate); 399 } 400 updateVisibilities(boolean animate)401 private void updateVisibilities(boolean animate) { 402 if (DEBUG) Log.d(TAG, String.format("updateVisibilities: animate=%b", animate)); 403 if (mBgAnimator != null) { 404 mBgAnimator.cancel(); 405 } 406 407 if (mUserSwitcherOpen) { 408 mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 0, 255); 409 mBgAnimator.setDuration(400); 410 mBgAnimator.setInterpolator(Interpolators.ALPHA_IN); 411 mBgAnimator.addListener(new AnimatorListenerAdapter() { 412 @Override 413 public void onAnimationEnd(Animator animation) { 414 mBgAnimator = null; 415 } 416 }); 417 mBgAnimator.start(); 418 } else { 419 mBgAnimator = ObjectAnimator.ofInt(mBackground, "alpha", 255, 0); 420 mBgAnimator.setDuration(400); 421 mBgAnimator.setInterpolator(Interpolators.ALPHA_OUT); 422 mBgAnimator.addListener(new AnimatorListenerAdapter() { 423 @Override 424 public void onAnimationEnd(Animator animation) { 425 mBgAnimator = null; 426 } 427 }); 428 mBgAnimator.start(); 429 } 430 mListView.updateVisibilities(mUserSwitcherOpen, animate); 431 } 432 isUserSwitcherOpen()433 private boolean isUserSwitcherOpen() { 434 return mUserSwitcherOpen; 435 } 436 437 static class KeyguardUserAdapter extends 438 UserSwitcherController.BaseUserAdapter implements View.OnClickListener { 439 440 private final Context mContext; 441 private final Resources mResources; 442 private final LayoutInflater mLayoutInflater; 443 private KeyguardUserSwitcherController mKeyguardUserSwitcherController; 444 private View mCurrentUserView; 445 // List of users where the first entry is always the current user 446 private ArrayList<UserSwitcherController.UserRecord> mUsersOrdered = new ArrayList<>(); 447 KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater, UserSwitcherController controller, KeyguardUserSwitcherController keyguardUserSwitcherController)448 KeyguardUserAdapter(Context context, Resources resources, LayoutInflater layoutInflater, 449 UserSwitcherController controller, 450 KeyguardUserSwitcherController keyguardUserSwitcherController) { 451 super(controller); 452 mContext = context; 453 mResources = resources; 454 mLayoutInflater = layoutInflater; 455 mKeyguardUserSwitcherController = keyguardUserSwitcherController; 456 } 457 458 @Override notifyDataSetChanged()459 public void notifyDataSetChanged() { 460 // At this point, value of isSimpleUserSwitcher() may have changed in addition to the 461 // data set 462 refreshUserOrder(); 463 super.notifyDataSetChanged(); 464 } 465 refreshUserOrder()466 void refreshUserOrder() { 467 ArrayList<UserSwitcherController.UserRecord> users = super.getUsers(); 468 mUsersOrdered = new ArrayList<>(users.size()); 469 for (int i = 0; i < users.size(); i++) { 470 UserSwitcherController.UserRecord record = users.get(i); 471 if (record.isCurrent) { 472 mUsersOrdered.add(0, record); 473 } else { 474 mUsersOrdered.add(record); 475 } 476 } 477 } 478 479 @Override getUsers()480 protected ArrayList<UserSwitcherController.UserRecord> getUsers() { 481 return mUsersOrdered; 482 } 483 484 @Override getView(int position, View convertView, ViewGroup parent)485 public View getView(int position, View convertView, ViewGroup parent) { 486 UserSwitcherController.UserRecord item = getItem(position); 487 return createUserDetailItemView(convertView, parent, item); 488 } 489 convertOrInflate(View convertView, ViewGroup parent)490 KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) { 491 if (!(convertView instanceof KeyguardUserDetailItemView) 492 || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) { 493 convertView = mLayoutInflater.inflate( 494 R.layout.keyguard_user_switcher_item, parent, false); 495 } 496 return (KeyguardUserDetailItemView) convertView; 497 } 498 createUserDetailItemView(View convertView, ViewGroup parent, UserSwitcherController.UserRecord item)499 KeyguardUserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent, 500 UserSwitcherController.UserRecord item) { 501 KeyguardUserDetailItemView v = convertOrInflate(convertView, parent); 502 v.setOnClickListener(this); 503 504 String name = getName(mContext, item); 505 if (item.picture == null) { 506 v.bind(name, getDrawable(item).mutate(), item.resolveId()); 507 } else { 508 int avatarSize = 509 (int) mResources.getDimension(R.dimen.kg_framed_avatar_size); 510 Drawable drawable = new CircleFramedDrawable(item.picture, avatarSize); 511 drawable.setColorFilter( 512 item.isSwitchToEnabled ? null : getDisabledUserAvatarColorFilter()); 513 v.bind(name, drawable, item.info.id); 514 } 515 v.setActivated(item.isCurrent); 516 v.setDisabledByAdmin(item.isDisabledByAdmin); 517 v.setEnabled(item.isSwitchToEnabled); 518 v.setAlpha(v.isEnabled() ? USER_SWITCH_ENABLED_ALPHA : USER_SWITCH_DISABLED_ALPHA); 519 520 if (item.isCurrent) { 521 mCurrentUserView = v; 522 } 523 v.setTag(item); 524 return v; 525 } 526 getDrawable(UserSwitcherController.UserRecord item)527 private Drawable getDrawable(UserSwitcherController.UserRecord item) { 528 Drawable drawable; 529 if (item.isCurrent && item.isGuest) { 530 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user); 531 } else { 532 drawable = getIconDrawable(mContext, item); 533 } 534 535 int iconColorRes; 536 if (item.isSwitchToEnabled) { 537 iconColorRes = R.color.kg_user_switcher_avatar_icon_color; 538 } else { 539 iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color; 540 } 541 drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme())); 542 543 Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar); 544 drawable = new LayerDrawable(new Drawable[]{bg, drawable}); 545 return drawable; 546 } 547 548 @Override onClick(View v)549 public void onClick(View v) { 550 UserSwitcherController.UserRecord user = (UserSwitcherController.UserRecord) v.getTag(); 551 552 if (mKeyguardUserSwitcherController.isListAnimating()) { 553 return; 554 } 555 556 if (mKeyguardUserSwitcherController.isUserSwitcherOpen()) { 557 if (!user.isCurrent || user.isGuest) { 558 onUserListItemClicked(user); 559 } else { 560 mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple( 561 true /* animate */); 562 } 563 } else { 564 // If switcher is closed, tapping anywhere in the view will open it 565 mKeyguardUserSwitcherController.setUserSwitcherOpened( 566 true /* open */, true /* animate */); 567 } 568 } 569 } 570 } 571