blob: 880e0d2eef31ba288105ac245f7bcbf967cf5206 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.policy;
import android.content.Context;
import android.content.res.Resources;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.R;
import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.UserAvatarView;
import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
/**
* Manages the user switch on the Keyguard that is used for opening the QS user panel.
*/
@KeyguardUserSwitcherScope
public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> {
private static final String TAG = "KeyguardQsUserSwitchController";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final AnimationProperties ANIMATION_PROPERTIES =
new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
private final Context mContext;
private Resources mResources;
private final UserSwitcherController mUserSwitcherController;
private BaseUserSwitcherAdapter mAdapter;
private final KeyguardStateController mKeyguardStateController;
private final FalsingManager mFalsingManager;
protected final SysuiStatusBarStateController mStatusBarStateController;
private final ConfigurationController mConfigurationController;
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
private final UserSwitchDialogController mUserSwitchDialogController;
private final UiEventLogger mUiEventLogger;
@VisibleForTesting
UserAvatarView mUserAvatarView;
private View mUserAvatarViewWithBackground;
UserRecord mCurrentUser;
private boolean mIsKeyguardShowing;
// State info for the user switch and keyguard
private int mBarState;
private final StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onStateChanged(int newState) {
boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
int oldState = mBarState;
mBarState = newState;
setKeyguardQsUserSwitchVisibility(
newState,
keyguardFadingAway,
goingToFullShade,
oldState);
}
};
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
public void onUiModeChanged() {
// Force update when dark theme toggled. Otherwise, icon will not update
// until it is clicked
if (mIsKeyguardShowing) {
updateView();
}
}
};
private final KeyguardStateController.Callback mKeyguardStateCallback =
new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
updateKeyguardShowing(false /* forceViewUpdate */);
}
@Override
public void onKeyguardShowingChanged() {
updateKeyguardShowing(false /* forceViewUpdate */);
}
@Override
public void onKeyguardFadingAwayChanged() {
updateKeyguardShowing(false /* forceViewUpdate */);
}
};
@Inject
public 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) {
super(view);
if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
mContext = context;
mResources = resources;
mUserSwitcherController = userSwitcherController;
mKeyguardStateController = keyguardStateController;
mFalsingManager = falsingManager;
mConfigurationController = configurationController;
mStatusBarStateController = statusBarStateController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
keyguardStateController, dozeParameters,
screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null);
mUserSwitchDialogController = userSwitchDialogController;
mUiEventLogger = uiEventLogger;
}
@Override
protected void onInit() {
super.onInit();
if (DEBUG) Log.d(TAG, "onInit");
mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
mUserAvatarViewWithBackground = mView.findViewById(
R.id.kg_multi_user_avatar_with_background);
mAdapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
};
mUserAvatarView.setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
if (isListAnimating()) {
return;
}
// Tapping anywhere in the view will open the user switcher
mUiEventLogger.log(
LockscreenGestureLogger.LockscreenUiEvent.LOCKSCREEN_SWITCH_USER_TAP);
mUserSwitchDialogController.showDialog(mUserAvatarViewWithBackground.getContext(),
Expandable.fromView(mUserAvatarViewWithBackground));
});
mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
AccessibilityNodeInfo.ACTION_CLICK,
mContext.getString(
R.string.accessibility_quick_settings_choose_user_action)));
}
});
}
@Override
protected void onViewAttached() {
if (DEBUG) Log.d(TAG, "onViewAttached");
mAdapter.registerDataSetObserver(mDataSetObserver);
mDataSetObserver.onChanged();
mStatusBarStateController.addCallback(mStatusBarStateListener);
mConfigurationController.addCallback(mConfigurationListener);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
// Force update when view attached in case configuration changed while the view was detached
updateCurrentUser();
updateKeyguardShowing(true /* forceViewUpdate */);
}
@Override
protected void onViewDetached() {
if (DEBUG) Log.d(TAG, "onViewDetached");
mAdapter.unregisterDataSetObserver(mDataSetObserver);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mConfigurationController.removeCallback(mConfigurationListener);
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
}
public final DataSetObserver mDataSetObserver = new DataSetObserver() {
@Override
public void onChanged() {
boolean userChanged = updateCurrentUser();
if (userChanged || (mIsKeyguardShowing && mUserAvatarView.isEmpty())) {
updateView();
}
}
};
private void clearAvatar() {
if (DEBUG) Log.d(TAG, "clearAvatar");
mUserAvatarView.setAvatar(null);
}
/**
* @param forceViewUpdate whether view should be updated regardless of whether
* keyguard-showing state changed
*/
@VisibleForTesting
void updateKeyguardShowing(boolean forceViewUpdate) {
boolean wasKeyguardShowing = mIsKeyguardShowing;
mIsKeyguardShowing = mKeyguardStateController.isShowing()
|| mKeyguardStateController.isKeyguardGoingAway();
if (wasKeyguardShowing == mIsKeyguardShowing && !forceViewUpdate) {
return;
}
if (DEBUG) {
Log.d(TAG, "updateKeyguardShowing:"
+ " mIsKeyguardShowing=" + mIsKeyguardShowing
+ " forceViewUpdate=" + forceViewUpdate);
}
if (mIsKeyguardShowing) {
updateView();
} else {
clearAvatar();
}
}
/**
* @return true if the current user has changed
*/
private boolean updateCurrentUser() {
UserRecord previousUser = mCurrentUser;
mCurrentUser = null;
for (int i = 0; i < mAdapter.getCount(); i++) {
UserRecord r = mAdapter.getItem(i);
if (r.isCurrent) {
mCurrentUser = r;
return !mCurrentUser.equals(previousUser);
}
}
return mCurrentUser == null && previousUser != null;
}
private String getContentDescription() {
if (mCurrentUser != null && mCurrentUser.info != null
&& !TextUtils.isEmpty(mCurrentUser.info.name)) {
// If we know the current user's name, have TalkBack to announce "Signed in as [user
// name]" when the icon is selected
return mContext.getString(
R.string.accessibility_quick_settings_user, mCurrentUser.info.name);
} else {
// As a fallback, have TalkBack announce "Switch user"
return mContext.getString(R.string.accessibility_multi_user_switch_switcher);
}
}
private void updateView() {
if (DEBUG) Log.d(TAG, "updateView");
mUserAvatarView.setContentDescription(getContentDescription());
int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
}
Drawable getCurrentUserIcon() {
Drawable drawable;
if (mCurrentUser == null || mCurrentUser.picture == null) {
if (mCurrentUser != null && mCurrentUser.isGuest) {
drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
} else {
drawable = mContext.getDrawable(R.drawable.ic_avatar_user);
}
int iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
} else {
int avatarSize = (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
}
Drawable bg = mContext.getDrawable(com.android.settingslib.R.drawable.user_avatar_bg);
drawable = new LayerDrawable(new Drawable[]{bg, drawable});
return drawable;
}
/**
* Get the height of the keyguard user switcher view when closed.
*/
public int getUserIconHeight() {
return mUserAvatarView.getHeight();
}
/**
* Set the visibility of the user avatar view based on some new state.
*/
public void setKeyguardQsUserSwitchVisibility(
int statusBarState,
boolean keyguardFadingAway,
boolean goingToFullShade,
int oldStatusBarState) {
mKeyguardVisibilityHelper.setViewVisibility(
statusBarState, keyguardFadingAway, goingToFullShade, oldStatusBarState);
}
/**
* Update position of the view with an optional animation
*/
public void updatePosition(int x, int y, boolean animate) {
PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, ANIMATION_PROPERTIES, animate);
PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, -Math.abs(x),
ANIMATION_PROPERTIES, animate);
}
/**
* Set keyguard user avatar view alpha.
*/
public void setAlpha(float alpha) {
if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
mView.setAlpha(alpha);
}
}
private boolean isListAnimating() {
return mKeyguardVisibilityHelper.isVisibilityAnimating();
}
}