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.content.Context; 20 import android.content.res.Resources; 21 import android.database.DataSetObserver; 22 import android.graphics.drawable.Drawable; 23 import android.graphics.drawable.LayerDrawable; 24 import android.os.UserHandle; 25 import android.text.TextUtils; 26 import android.util.Log; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.view.accessibility.AccessibilityNodeInfo; 30 import android.widget.FrameLayout; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.logging.UiEventLogger; 34 import com.android.keyguard.KeyguardConstants; 35 import com.android.keyguard.KeyguardVisibilityHelper; 36 import com.android.keyguard.dagger.KeyguardUserSwitcherScope; 37 import com.android.settingslib.drawable.CircleFramedDrawable; 38 import com.android.systemui.R; 39 import com.android.systemui.animation.Expandable; 40 import com.android.systemui.dagger.qualifiers.Main; 41 import com.android.systemui.plugins.FalsingManager; 42 import com.android.systemui.plugins.statusbar.StatusBarStateController; 43 import com.android.systemui.qs.user.UserSwitchDialogController; 44 import com.android.systemui.statusbar.SysuiStatusBarStateController; 45 import com.android.systemui.statusbar.notification.AnimatableProperty; 46 import com.android.systemui.statusbar.notification.PropertyAnimator; 47 import com.android.systemui.statusbar.notification.stack.AnimationProperties; 48 import com.android.systemui.statusbar.notification.stack.StackStateAnimator; 49 import com.android.systemui.statusbar.phone.DozeParameters; 50 import com.android.systemui.statusbar.phone.LockscreenGestureLogger; 51 import com.android.systemui.statusbar.phone.ScreenOffAnimationController; 52 import com.android.systemui.statusbar.phone.UserAvatarView; 53 import com.android.systemui.user.data.source.UserRecord; 54 import com.android.systemui.util.ViewController; 55 56 import javax.inject.Inject; 57 58 /** 59 * Manages the user switch on the Keyguard that is used for opening the QS user panel. 60 */ 61 @KeyguardUserSwitcherScope 62 public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> { 63 64 private static final String TAG = "KeyguardQsUserSwitchController"; 65 private static final boolean DEBUG = KeyguardConstants.DEBUG; 66 67 private static final AnimationProperties ANIMATION_PROPERTIES = 68 new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 69 70 private final Context mContext; 71 private Resources mResources; 72 private final UserSwitcherController mUserSwitcherController; 73 private BaseUserSwitcherAdapter mAdapter; 74 private final KeyguardStateController mKeyguardStateController; 75 private final FalsingManager mFalsingManager; 76 protected final SysuiStatusBarStateController mStatusBarStateController; 77 private final ConfigurationController mConfigurationController; 78 private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; 79 private final UserSwitchDialogController mUserSwitchDialogController; 80 private final UiEventLogger mUiEventLogger; 81 @VisibleForTesting 82 UserAvatarView mUserAvatarView; 83 private View mUserAvatarViewWithBackground; 84 UserRecord mCurrentUser; 85 private boolean mIsKeyguardShowing; 86 87 // State info for the user switch and keyguard 88 private int mBarState; 89 90 private final StatusBarStateController.StateListener mStatusBarStateListener = 91 new StatusBarStateController.StateListener() { 92 @Override 93 public void onStateChanged(int newState) { 94 boolean goingToFullShade = mStatusBarStateController.goingToFullShade(); 95 boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway(); 96 int oldState = mBarState; 97 mBarState = newState; 98 99 setKeyguardQsUserSwitchVisibility( 100 newState, 101 keyguardFadingAway, 102 goingToFullShade, 103 oldState); 104 } 105 }; 106 107 private ConfigurationController.ConfigurationListener mConfigurationListener = 108 new ConfigurationController.ConfigurationListener() { 109 110 @Override 111 public void onUiModeChanged() { 112 // Force update when dark theme toggled. Otherwise, icon will not update 113 // until it is clicked 114 if (mIsKeyguardShowing) { 115 updateView(); 116 } 117 } 118 }; 119 120 private final KeyguardStateController.Callback mKeyguardStateCallback = 121 new KeyguardStateController.Callback() { 122 @Override 123 public void onUnlockedChanged() { 124 updateKeyguardShowing(false /* forceViewUpdate */); 125 } 126 127 @Override 128 public void onKeyguardShowingChanged() { 129 updateKeyguardShowing(false /* forceViewUpdate */); 130 } 131 132 @Override 133 public void onKeyguardFadingAwayChanged() { 134 updateKeyguardShowing(false /* forceViewUpdate */); 135 } 136 }; 137 138 @Inject KeyguardQsUserSwitchController( FrameLayout view, Context context, @Main Resources resources, UserSwitcherController userSwitcherController, KeyguardStateController keyguardStateController, FalsingManager falsingManager, ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, UserSwitchDialogController userSwitchDialogController, UiEventLogger uiEventLogger)139 public KeyguardQsUserSwitchController( 140 FrameLayout view, 141 Context context, 142 @Main Resources resources, 143 UserSwitcherController userSwitcherController, 144 KeyguardStateController keyguardStateController, 145 FalsingManager falsingManager, 146 ConfigurationController configurationController, 147 SysuiStatusBarStateController statusBarStateController, 148 DozeParameters dozeParameters, 149 ScreenOffAnimationController screenOffAnimationController, 150 UserSwitchDialogController userSwitchDialogController, 151 UiEventLogger uiEventLogger) { 152 super(view); 153 if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); 154 mContext = context; 155 mResources = resources; 156 mUserSwitcherController = userSwitcherController; 157 mKeyguardStateController = keyguardStateController; 158 mFalsingManager = falsingManager; 159 mConfigurationController = configurationController; 160 mStatusBarStateController = statusBarStateController; 161 mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, 162 keyguardStateController, dozeParameters, 163 screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null); 164 mUserSwitchDialogController = userSwitchDialogController; 165 mUiEventLogger = uiEventLogger; 166 } 167 168 @Override onInit()169 protected void onInit() { 170 super.onInit(); 171 if (DEBUG) Log.d(TAG, "onInit"); 172 mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar); 173 mUserAvatarViewWithBackground = mView.findViewById( 174 R.id.kg_multi_user_avatar_with_background); 175 mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) { 176 @Override 177 public View getView(int position, View convertView, ViewGroup parent) { 178 return null; 179 } 180 }; 181 182 mUserAvatarView.setOnClickListener(v -> { 183 if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { 184 return; 185 } 186 if (isListAnimating()) { 187 return; 188 } 189 190 // Tapping anywhere in the view will open the user switcher 191 mUiEventLogger.log( 192 LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP); 193 194 mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(), 195 Expandable.fromView(mUserAvatarViewWithBackground)); 196 }); 197 198 mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() { 199 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 200 super.onInitializeAccessibilityNodeInfo(host, info); 201 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 202 AccessibilityNodeInfo.ACTION_CLICK, 203 mContext.getString( 204 R.string.accessibility_quick_settings_choose_user_action))); 205 } 206 }); 207 } 208 209 @Override onViewAttached()210 protected void onViewAttached() { 211 if (DEBUG) Log.d(TAG, "onViewAttached"); 212 mAdapter.registerDataSetObserver(mDataSetObserver); 213 mDataSetObserver.onChanged(); 214 mStatusBarStateController.addCallback(mStatusBarStateListener); 215 mConfigurationController.addCallback(mConfigurationListener); 216 mKeyguardStateController.addCallback(mKeyguardStateCallback); 217 // Force update when view attached in case configuration changed while the view was detached 218 updateCurrentUser(); 219 updateKeyguardShowing(true /* forceViewUpdate */); 220 } 221 222 @Override onViewDetached()223 protected void onViewDetached() { 224 if (DEBUG) Log.d(TAG, "onViewDetached"); 225 226 mAdapter.unregisterDataSetObserver(mDataSetObserver); 227 mStatusBarStateController.removeCallback(mStatusBarStateListener); 228 mConfigurationController.removeCallback(mConfigurationListener); 229 mKeyguardStateController.removeCallback(mKeyguardStateCallback); 230 } 231 232 public final DataSetObserver mDataSetObserver = new DataSetObserver() { 233 @Override 234 public void onChanged() { 235 boolean userChanged = updateCurrentUser(); 236 if (userChanged || (mIsKeyguardShowing && mUserAvatarView.isEmpty())) { 237 updateView(); 238 } 239 } 240 }; 241 clearAvatar()242 private void clearAvatar() { 243 if (DEBUG) Log.d(TAG, "clearAvatar"); 244 mUserAvatarView.setAvatar(null); 245 } 246 247 /** 248 * @param forceViewUpdate whether view should be updated regardless of whether 249 * keyguard-showing state changed 250 */ 251 @VisibleForTesting updateKeyguardShowing(boolean forceViewUpdate)252 void updateKeyguardShowing(boolean forceViewUpdate) { 253 boolean wasKeyguardShowing = mIsKeyguardShowing; 254 mIsKeyguardShowing = mKeyguardStateController.isShowing() 255 || mKeyguardStateController.isKeyguardGoingAway(); 256 if (wasKeyguardShowing == mIsKeyguardShowing && !forceViewUpdate) { 257 return; 258 } 259 if (DEBUG) { 260 Log.d(TAG, "updateKeyguardShowing:" 261 + " mIsKeyguardShowing=" + mIsKeyguardShowing 262 + " forceViewUpdate=" + forceViewUpdate); 263 } 264 if (mIsKeyguardShowing) { 265 updateView(); 266 } else { 267 clearAvatar(); 268 } 269 } 270 271 /** 272 * @return true if the current user has changed 273 */ updateCurrentUser()274 private boolean updateCurrentUser() { 275 UserRecord previousUser = mCurrentUser; 276 mCurrentUser = null; 277 for (int i = 0; i < mAdapter.getCount(); i++) { 278 UserRecord r = mAdapter.getItem(i); 279 if (r.isCurrent) { 280 mCurrentUser = r; 281 return !mCurrentUser.equals(previousUser); 282 } 283 } 284 return mCurrentUser == null && previousUser != null; 285 } 286 getContentDescription()287 private String getContentDescription() { 288 if (mCurrentUser != null && mCurrentUser.info != null 289 && !TextUtils.isEmpty(mCurrentUser.info.name)) { 290 // If we know the current user's name, have TalkBack to announce "Signed in as [user 291 // name]" when the icon is selected 292 return mContext.getString( 293 R.string.accessibility_quick_settings_user, mCurrentUser.info.name); 294 } else { 295 // As a fallback, have TalkBack announce "Switch user" 296 return mContext.getString(R.string.accessibility_multi_user_switch_switcher); 297 } 298 } 299 updateView()300 private void updateView() { 301 if (DEBUG) Log.d(TAG, "updateView"); 302 mUserAvatarView.setContentDescription(getContentDescription()); 303 int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL; 304 mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId); 305 } 306 getCurrentUserIcon()307 Drawable getCurrentUserIcon() { 308 Drawable drawable; 309 if (mCurrentUser == null || mCurrentUser.picture == null) { 310 if (mCurrentUser != null && mCurrentUser.isGuest) { 311 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user); 312 } else { 313 drawable = mContext.getDrawable(R.drawable.ic_avatar_user); 314 } 315 int iconColorRes = R.color.kg_user_switcher_avatar_icon_color; 316 drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme())); 317 } else { 318 int avatarSize = (int) mResources.getDimension(R.dimen.kg_framed_avatar_size); 319 drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize); 320 } 321 322 Drawable bg = mContext.getDrawable(R.drawable.user_avatar_bg); 323 drawable = new LayerDrawable(new Drawable[]{bg, drawable}); 324 return drawable; 325 } 326 327 /** 328 * Get the height of the keyguard user switcher view when closed. 329 */ getUserIconHeight()330 public int getUserIconHeight() { 331 return mUserAvatarView.getHeight(); 332 } 333 334 /** 335 * Set the visibility of the user avatar view based on some new state. 336 */ setKeyguardQsUserSwitchVisibility( int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState)337 public void setKeyguardQsUserSwitchVisibility( 338 int statusBarState, 339 boolean keyguardFadingAway, 340 boolean goingToFullShade, 341 int oldStatusBarState) { 342 mKeyguardVisibilityHelper.setViewVisibility( 343 statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState); 344 } 345 346 /** 347 * Update position of the view with an optional animation 348 */ updatePosition(int x, int y, boolean animate)349 public void updatePosition(int x, int y, boolean animate) { 350 PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate); 351 PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x), 352 ANIMATION_PROPERTIES, animate); 353 } 354 355 /** 356 * Set keyguard user avatar view alpha. 357 */ setAlpha(float alpha)358 public void setAlpha(float alpha) { 359 if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { 360 mView.setAlpha(alpha); 361 } 362 } 363 isListAnimating()364 private boolean isListAnimating() { 365 return mKeyguardVisibilityHelper.isVisibilityAnimating(); 366 } 367 } 368