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.animation.Expandable; 39 import com.android.systemui.dagger.qualifiers.Main; 40 import com.android.systemui.plugins.FalsingManager; 41 import com.android.systemui.plugins.statusbar.StatusBarStateController; 42 import com.android.systemui.qs.user.UserSwitchDialogController; 43 import com.android.systemui.res.R; 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, 164 /* logBuffer= */ null); 165 mUserSwitchDialogController = userSwitchDialogController; 166 mUiEventLogger = uiEventLogger; 167 } 168 169 @Override onInit()170 protected void onInit() { 171 super.onInit(); 172 if (DEBUG) Log.d(TAG, "onInit"); 173 mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar); 174 mUserAvatarViewWithBackground = mView.findViewById( 175 R.id.kg_multi_user_avatar_with_background); 176 mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) { 177 @Override 178 public View getView(int position, View convertView, ViewGroup parent) { 179 return null; 180 } 181 }; 182 183 mUserAvatarView.setOnClickListener(v -> { 184 if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { 185 return; 186 } 187 if (isListAnimating()) { 188 return; 189 } 190 191 // Tapping anywhere in the view will open the user switcher 192 mUiEventLogger.log( 193 LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP); 194 195 mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(), 196 Expandable.fromView(mUserAvatarViewWithBackground)); 197 }); 198 199 mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() { 200 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 201 super.onInitializeAccessibilityNodeInfo(host, info); 202 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 203 AccessibilityNodeInfo.ACTION_CLICK, 204 mContext.getString( 205 R.string.accessibility_quick_settings_choose_user_action))); 206 } 207 }); 208 } 209 210 @Override onViewAttached()211 protected void onViewAttached() { 212 if (DEBUG) Log.d(TAG, "onViewAttached"); 213 mAdapter.registerDataSetObserver(mDataSetObserver); 214 mDataSetObserver.onChanged(); 215 mStatusBarStateController.addCallback(mStatusBarStateListener); 216 mConfigurationController.addCallback(mConfigurationListener); 217 mKeyguardStateController.addCallback(mKeyguardStateCallback); 218 // Force update when view attached in case configuration changed while the view was detached 219 updateCurrentUser(); 220 updateKeyguardShowing(true /* forceViewUpdate */); 221 } 222 223 @Override onViewDetached()224 protected void onViewDetached() { 225 if (DEBUG) Log.d(TAG, "onViewDetached"); 226 227 mAdapter.unregisterDataSetObserver(mDataSetObserver); 228 mStatusBarStateController.removeCallback(mStatusBarStateListener); 229 mConfigurationController.removeCallback(mConfigurationListener); 230 mKeyguardStateController.removeCallback(mKeyguardStateCallback); 231 } 232 233 public final DataSetObserver mDataSetObserver = new DataSetObserver() { 234 @Override 235 public void onChanged() { 236 boolean userChanged = updateCurrentUser(); 237 if (userChanged || (mIsKeyguardShowing && mUserAvatarView.isEmpty())) { 238 updateView(); 239 } 240 } 241 }; 242 clearAvatar()243 private void clearAvatar() { 244 if (DEBUG) Log.d(TAG, "clearAvatar"); 245 mUserAvatarView.setAvatar(null); 246 } 247 248 /** 249 * @param forceViewUpdate whether view should be updated regardless of whether 250 * keyguard-showing state changed 251 */ 252 @VisibleForTesting updateKeyguardShowing(boolean forceViewUpdate)253 void updateKeyguardShowing(boolean forceViewUpdate) { 254 boolean wasKeyguardShowing = mIsKeyguardShowing; 255 mIsKeyguardShowing = mKeyguardStateController.isShowing() 256 || mKeyguardStateController.isKeyguardGoingAway(); 257 if (wasKeyguardShowing == mIsKeyguardShowing && !forceViewUpdate) { 258 return; 259 } 260 if (DEBUG) { 261 Log.d(TAG, "updateKeyguardShowing:" 262 + " mIsKeyguardShowing=" + mIsKeyguardShowing 263 + " forceViewUpdate=" + forceViewUpdate); 264 } 265 if (mIsKeyguardShowing) { 266 updateView(); 267 } else { 268 clearAvatar(); 269 } 270 } 271 272 /** 273 * @return true if the current user has changed 274 */ updateCurrentUser()275 private boolean updateCurrentUser() { 276 UserRecord previousUser = mCurrentUser; 277 mCurrentUser = null; 278 for (int i = 0; i < mAdapter.getCount(); i++) { 279 UserRecord r = mAdapter.getItem(i); 280 if (r.isCurrent) { 281 mCurrentUser = r; 282 return !mCurrentUser.equals(previousUser); 283 } 284 } 285 return mCurrentUser == null && previousUser != null; 286 } 287 getContentDescription()288 private String getContentDescription() { 289 if (mCurrentUser != null && mCurrentUser.info != null 290 && !TextUtils.isEmpty(mCurrentUser.info.name)) { 291 // If we know the current user's name, have TalkBack to announce "Signed in as [user 292 // name]" when the icon is selected 293 return mContext.getString( 294 R.string.accessibility_quick_settings_user, mCurrentUser.info.name); 295 } else { 296 // As a fallback, have TalkBack announce "Switch user" 297 return mContext.getString(R.string.accessibility_multi_user_switch_switcher); 298 } 299 } 300 updateView()301 private void updateView() { 302 if (DEBUG) Log.d(TAG, "updateView"); 303 mUserAvatarView.setContentDescription(getContentDescription()); 304 int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL; 305 mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId); 306 } 307 getCurrentUserIcon()308 Drawable getCurrentUserIcon() { 309 Drawable drawable; 310 if (mCurrentUser == null || mCurrentUser.picture == null) { 311 if (mCurrentUser != null && mCurrentUser.isGuest) { 312 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user); 313 } else { 314 drawable = mContext.getDrawable(R.drawable.ic_avatar_user); 315 } 316 int iconColorRes = R.color.kg_user_switcher_avatar_icon_color; 317 drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme())); 318 } else { 319 int avatarSize = (int) mResources.getDimension(R.dimen.kg_framed_avatar_size); 320 drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize); 321 } 322 323 Drawable bg = mContext.getDrawable(com.android.settingslib.R.drawable.user_avatar_bg); 324 drawable = new LayerDrawable(new Drawable[]{bg, drawable}); 325 return drawable; 326 } 327 328 /** 329 * Get the height of the keyguard user switcher view when closed. 330 */ getUserIconHeight()331 public int getUserIconHeight() { 332 return mUserAvatarView.getHeight(); 333 } 334 335 /** 336 * Set the visibility of the user avatar view based on some new state. 337 */ setKeyguardQsUserSwitchVisibility( int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState)338 public void setKeyguardQsUserSwitchVisibility( 339 int statusBarState, 340 boolean keyguardFadingAway, 341 boolean goingToFullShade, 342 int oldStatusBarState) { 343 mKeyguardVisibilityHelper.setViewVisibility( 344 statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState); 345 } 346 347 /** 348 * Update position of the view with an optional animation 349 */ updatePosition(int x, int y, boolean animate)350 public void updatePosition(int x, int y, boolean animate) { 351 PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate); 352 PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x), 353 ANIMATION_PROPERTIES, animate); 354 } 355 356 /** 357 * Set keyguard user avatar view alpha. 358 */ setAlpha(float alpha)359 public void setAlpha(float alpha) { 360 if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { 361 mView.setAlpha(alpha); 362 } 363 } 364 isListAnimating()365 private boolean isListAnimating() { 366 return mKeyguardVisibilityHelper.isVisibilityAnimating(); 367 } 368 } 369