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