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.keyguard.KeyguardConstants; 33 import com.android.keyguard.KeyguardVisibilityHelper; 34 import com.android.keyguard.dagger.KeyguardUserSwitcherScope; 35 import com.android.settingslib.drawable.CircleFramedDrawable; 36 import com.android.systemui.R; 37 import com.android.systemui.dagger.qualifiers.Main; 38 import com.android.systemui.flags.FeatureFlags; 39 import com.android.systemui.keyguard.ScreenLifecycle; 40 import com.android.systemui.plugins.FalsingManager; 41 import com.android.systemui.plugins.statusbar.StatusBarStateController; 42 import com.android.systemui.qs.tiles.UserDetailView; 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.NotificationPanelViewController; 51 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; 52 import com.android.systemui.statusbar.phone.UserAvatarView; 53 import com.android.systemui.util.ViewController; 54 55 import javax.inject.Inject; 56 import javax.inject.Provider; 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 final ScreenLifecycle mScreenLifecycle; 74 private UserSwitcherController.BaseUserAdapter mAdapter; 75 private final KeyguardStateController mKeyguardStateController; 76 private final FalsingManager mFalsingManager; 77 protected final SysuiStatusBarStateController mStatusBarStateController; 78 private final ConfigurationController mConfigurationController; 79 private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; 80 private final KeyguardUserDetailAdapter mUserDetailAdapter; 81 private final FeatureFlags mFeatureFlags; 82 private final UserSwitchDialogController mUserSwitchDialogController; 83 private NotificationPanelViewController mNotificationPanelViewController; 84 private UserAvatarView mUserAvatarView; 85 UserSwitcherController.UserRecord mCurrentUser; 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 if (DEBUG) Log.d(TAG, String.format("onStateChanged: newState=%d", newState)); 95 96 boolean goingToFullShade = mStatusBarStateController.goingToFullShade(); 97 boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway(); 98 int oldState = mBarState; 99 mBarState = newState; 100 101 setKeyguardQsUserSwitchVisibility( 102 newState, 103 keyguardFadingAway, 104 goingToFullShade, 105 oldState); 106 } 107 }; 108 109 private ConfigurationController.ConfigurationListener 110 mConfigurationListener = new ConfigurationController.ConfigurationListener() { 111 112 @Override 113 public void onUiModeChanged() { 114 updateView(true); 115 } 116 }; 117 118 @Inject KeyguardQsUserSwitchController( FrameLayout view, Context context, @Main Resources resources, ScreenLifecycle screenLifecycle, UserSwitcherController userSwitcherController, KeyguardStateController keyguardStateController, FalsingManager falsingManager, ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, DozeParameters dozeParameters, Provider<UserDetailView.Adapter> userDetailViewAdapterProvider, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, FeatureFlags featureFlags, UserSwitchDialogController userSwitchDialogController)119 public KeyguardQsUserSwitchController( 120 FrameLayout view, 121 Context context, 122 @Main Resources resources, 123 ScreenLifecycle screenLifecycle, 124 UserSwitcherController userSwitcherController, 125 KeyguardStateController keyguardStateController, 126 FalsingManager falsingManager, 127 ConfigurationController configurationController, 128 SysuiStatusBarStateController statusBarStateController, 129 DozeParameters dozeParameters, 130 Provider<UserDetailView.Adapter> userDetailViewAdapterProvider, 131 UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, 132 FeatureFlags featureFlags, 133 UserSwitchDialogController userSwitchDialogController) { 134 super(view); 135 if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); 136 mContext = context; 137 mResources = resources; 138 mScreenLifecycle = screenLifecycle; 139 mUserSwitcherController = userSwitcherController; 140 mKeyguardStateController = keyguardStateController; 141 mFalsingManager = falsingManager; 142 mConfigurationController = configurationController; 143 mStatusBarStateController = statusBarStateController; 144 mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, 145 keyguardStateController, dozeParameters, 146 unlockedScreenOffAnimationController, /* animateYPos= */ false); 147 mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider); 148 mFeatureFlags = featureFlags; 149 mUserSwitchDialogController = userSwitchDialogController; 150 } 151 152 @Override onInit()153 protected void onInit() { 154 super.onInit(); 155 if (DEBUG) Log.d(TAG, "onInit"); 156 mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar); 157 mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) { 158 @Override 159 public View getView(int position, View convertView, ViewGroup parent) { 160 return null; 161 } 162 }; 163 164 mUserAvatarView.setOnClickListener(v -> { 165 if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { 166 return; 167 } 168 if (isListAnimating()) { 169 return; 170 } 171 172 // Tapping anywhere in the view will open QS user panel 173 if (mFeatureFlags.useNewUserSwitcher()) { 174 mUserSwitchDialogController.showDialog(mView); 175 } else { 176 openQsUserPanel(); 177 } 178 }); 179 180 mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() { 181 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 182 super.onInitializeAccessibilityNodeInfo(host, info); 183 info.addAction(new AccessibilityNodeInfo.AccessibilityAction( 184 AccessibilityNodeInfo.ACTION_CLICK, 185 mContext.getString( 186 R.string.accessibility_quick_settings_choose_user_action))); 187 } 188 }); 189 } 190 191 @Override onViewAttached()192 protected void onViewAttached() { 193 if (DEBUG) Log.d(TAG, "onViewAttached"); 194 mAdapter.registerDataSetObserver(mDataSetObserver); 195 mDataSetObserver.onChanged(); 196 mStatusBarStateController.addCallback(mStatusBarStateListener); 197 mConfigurationController.addCallback(mConfigurationListener); 198 updateView(true /* forceUpdate */); 199 } 200 201 @Override onViewDetached()202 protected void onViewDetached() { 203 if (DEBUG) Log.d(TAG, "onViewDetached"); 204 205 mAdapter.unregisterDataSetObserver(mDataSetObserver); 206 mStatusBarStateController.removeCallback(mStatusBarStateListener); 207 mConfigurationController.removeCallback(mConfigurationListener); 208 } 209 210 public final DataSetObserver mDataSetObserver = new DataSetObserver() { 211 @Override 212 public void onChanged() { 213 updateView(false /* forceUpdate */); 214 } 215 }; 216 217 /** 218 * @return true if the current user has changed 219 */ updateCurrentUser()220 private boolean updateCurrentUser() { 221 UserSwitcherController.UserRecord previousUser = mCurrentUser; 222 mCurrentUser = null; 223 for (int i = 0; i < mAdapter.getCount(); i++) { 224 UserSwitcherController.UserRecord r = mAdapter.getItem(i); 225 if (r.isCurrent) { 226 mCurrentUser = r; 227 return !mCurrentUser.equals(previousUser); 228 } 229 } 230 return mCurrentUser == null && previousUser != null; 231 } 232 233 /** 234 * @param forceUpdate whether to update view even if current user did not change 235 */ updateView(boolean forceUpdate)236 private void updateView(boolean forceUpdate) { 237 if (!updateCurrentUser() && !forceUpdate) { 238 return; 239 } 240 241 String contentDescription = null; 242 if (mCurrentUser != null && mCurrentUser.info != null && !TextUtils.isEmpty( 243 mCurrentUser.info.name)) { 244 // If we know the current user's name, have TalkBack to announce "Signed in as [user 245 // name]" when the icon is selected 246 contentDescription = mContext.getString(R.string.accessibility_quick_settings_user, 247 mCurrentUser.info.name); 248 } else { 249 // As a fallback, have TalkBack announce "Switch user" 250 contentDescription = mContext.getString( 251 R.string.accessibility_multi_user_switch_switcher); 252 } 253 254 if (!TextUtils.equals(mUserAvatarView.getContentDescription(), contentDescription)) { 255 mUserAvatarView.setContentDescription(contentDescription); 256 } 257 258 int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL; 259 mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId); 260 } 261 getCurrentUserIcon()262 Drawable getCurrentUserIcon() { 263 Drawable drawable; 264 if (mCurrentUser == null || mCurrentUser.picture == null) { 265 if (mCurrentUser != null && mCurrentUser.isGuest) { 266 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user); 267 } else { 268 drawable = mContext.getDrawable(R.drawable.ic_avatar_user); 269 } 270 int iconColorRes = R.color.kg_user_switcher_avatar_icon_color; 271 drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme())); 272 } else { 273 int avatarSize = (int) mResources.getDimension(R.dimen.kg_framed_avatar_size); 274 drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize); 275 } 276 277 Drawable bg = mContext.getDrawable(R.drawable.kg_bg_avatar); 278 drawable = new LayerDrawable(new Drawable[]{bg, drawable}); 279 return drawable; 280 } 281 282 /** 283 * Get the height of the keyguard user switcher view when closed. 284 */ getUserIconHeight()285 public int getUserIconHeight() { 286 return mUserAvatarView.getHeight(); 287 } 288 289 /** 290 * Set the visibility of the user avatar view based on some new state. 291 */ setKeyguardQsUserSwitchVisibility( int statusBarState, boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState)292 public void setKeyguardQsUserSwitchVisibility( 293 int statusBarState, 294 boolean keyguardFadingAway, 295 boolean goingToFullShade, 296 int oldStatusBarState) { 297 mKeyguardVisibilityHelper.setViewVisibility( 298 statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState); 299 } 300 301 /** 302 * Update position of the view with an optional animation 303 */ updatePosition(int x, int y, boolean animate)304 public void updatePosition(int x, int y, boolean animate) { 305 PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate); 306 PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x), 307 ANIMATION_PROPERTIES, animate); 308 } 309 310 /** 311 * Set keyguard user avatar view alpha. 312 */ setAlpha(float alpha)313 public void setAlpha(float alpha) { 314 if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { 315 mView.setAlpha(alpha); 316 } 317 } 318 isListAnimating()319 private boolean isListAnimating() { 320 return mKeyguardVisibilityHelper.isVisibilityAnimating(); 321 } 322 openQsUserPanel()323 private void openQsUserPanel() { 324 mNotificationPanelViewController.expandWithQsDetail(mUserDetailAdapter); 325 } 326 setNotificationPanelViewController( NotificationPanelViewController notificationPanelViewController)327 public void setNotificationPanelViewController( 328 NotificationPanelViewController notificationPanelViewController) { 329 mNotificationPanelViewController = notificationPanelViewController; 330 } 331 332 class KeyguardUserDetailAdapter extends UserSwitcherController.UserDetailAdapter { KeyguardUserDetailAdapter(Context context, Provider<UserDetailView.Adapter> userDetailViewAdapterProvider)333 KeyguardUserDetailAdapter(Context context, 334 Provider<UserDetailView.Adapter> userDetailViewAdapterProvider) { 335 super(context, userDetailViewAdapterProvider); 336 } 337 338 @Override shouldAnimate()339 public boolean shouldAnimate() { 340 return false; 341 } 342 343 @Override getDoneText()344 public int getDoneText() { 345 return R.string.quick_settings_close_user_panel; 346 } 347 348 @Override onDoneButtonClicked()349 public boolean onDoneButtonClicked() { 350 if (mNotificationPanelViewController != null) { 351 mNotificationPanelViewController.animateCloseQs(true /* animateAway */); 352 return true; 353 } else { 354 return false; 355 } 356 } 357 } 358 } 359