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