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