blob: 7a8dc32741bfada4750e09eb38d2f663374dce3e [file] [log] [blame]
/*
* Copyright (C) 2019 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.phone;
import static com.android.systemui.statusbar.phone.LockIcon.STATE_BIOMETRICS_ERROR;
import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCKED;
import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCK_OPEN;
import static com.android.systemui.statusbar.phone.LockIcon.STATE_SCANNING_FACE;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.hardware.biometrics.BiometricSourceType;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.Nullable;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
/** Controls the {@link LockIcon} in the lockscreen. */
@Singleton
public class LockscreenLockIconController {
private final LockscreenGestureLogger mLockscreenGestureLogger;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final LockPatternUtils mLockPatternUtils;
private final ShadeController mShadeController;
private final AccessibilityController mAccessibilityController;
private final KeyguardIndicationController mKeyguardIndicationController;
private final StatusBarStateController mStatusBarStateController;
private final ConfigurationController mConfigurationController;
private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
private final KeyguardBypassController mKeyguardBypassController;
private final Optional<DockManager> mDockManager;
private final KeyguardStateController mKeyguardStateController;
private final Resources mResources;
private final HeadsUpManagerPhone mHeadsUpManagerPhone;
private boolean mKeyguardShowing;
private boolean mKeyguardJustShown;
private boolean mBlockUpdates;
private boolean mSimLocked;
private boolean mTransientBiometricsError;
private boolean mDocked;
private boolean mWakeAndUnlockRunning;
private boolean mShowingLaunchAffordance;
private boolean mBouncerShowingScrimmed;
private boolean mFingerprintUnlock;
private int mStatusBarState = StatusBarState.SHADE;
private LockIcon mLockIcon;
private View.OnAttachStateChangeListener mOnAttachStateChangeListener =
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
mStatusBarStateController.addCallback(mSBStateListener);
mConfigurationController.addCallback(mConfigurationListener);
mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
mConfigurationListener.onThemeChanged();
update();
}
@Override
public void onViewDetachedFromWindow(View v) {
mStatusBarStateController.removeCallback(mSBStateListener);
mConfigurationController.removeCallback(mConfigurationListener);
mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
}
};
private final StatusBarStateController.StateListener mSBStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onDozingChanged(boolean isDozing) {
setDozing(isDozing);
}
@Override
public void onPulsingChanged(boolean pulsing) {
setPulsing(pulsing);
}
@Override
public void onDozeAmountChanged(float linear, float eased) {
if (mLockIcon != null) {
mLockIcon.setDozeAmount(eased);
}
}
@Override
public void onStateChanged(int newState) {
setStatusBarState(newState);
}
};
private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
private int mDensity;
@Override
public void onThemeChanged() {
if (mLockIcon == null) {
return;
}
TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
null, new int[]{ R.attr.wallpaperTextColor }, 0, 0);
int iconColor = typedArray.getColor(0, Color.WHITE);
typedArray.recycle();
mLockIcon.onThemeChange(iconColor);
}
@Override
public void onDensityOrFontScaleChanged() {
if (mLockIcon == null) {
return;
}
ViewGroup.LayoutParams lp = mLockIcon.getLayoutParams();
if (lp == null) {
return;
}
lp.width = mLockIcon.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width);
lp.height = mLockIcon.getResources().getDimensionPixelSize(
R.dimen.keyguard_lock_height);
mLockIcon.setLayoutParams(lp);
update(true /* force */);
}
@Override
public void onLocaleListChanged() {
if (mLockIcon == null) {
return;
}
mLockIcon.setContentDescription(
mLockIcon.getResources().getText(R.string.accessibility_unlock_button));
update(true /* force */);
}
@Override
public void onConfigChanged(Configuration newConfig) {
final int density = newConfig.densityDpi;
if (density != mDensity) {
mDensity = density;
update();
}
}
};
private final WakeUpListener mWakeUpListener = new WakeUpListener() {
@Override
public void onPulseExpansionChanged(boolean expandingChanged) {
}
@Override
public void onFullyHiddenChanged(boolean isFullyHidden) {
if (mKeyguardBypassController.getBypassEnabled()) {
boolean changed = updateIconVisibility();
if (changed) {
update();
}
}
}
};
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@Override
public void onSimStateChanged(int subId, int slotId, int simState) {
mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
update();
}
@Override
public void onKeyguardVisibilityChanged(boolean showing) {
update();
}
@Override
public void onBiometricRunningStateChanged(boolean running,
BiometricSourceType biometricSourceType) {
update();
}
@Override
public void onStrongAuthStateChanged(int userId) {
update();
}
};
private final DockManager.DockEventListener mDockEventListener =
event -> {
boolean docked =
event == DockManager.STATE_DOCKED || event == DockManager.STATE_DOCKED_HIDE;
if (docked != mDocked) {
mDocked = docked;
update();
}
};
private final KeyguardStateController.Callback mKeyguardMonitorCallback =
new KeyguardStateController.Callback() {
@Override
public void onKeyguardShowingChanged() {
boolean force = false;
boolean wasShowing = mKeyguardShowing;
mKeyguardShowing = mKeyguardStateController.isShowing();
if (!wasShowing && mKeyguardShowing && mBlockUpdates) {
mBlockUpdates = false;
force = true;
}
if (!wasShowing && mKeyguardShowing) {
mKeyguardJustShown = true;
}
update(force);
}
@Override
public void onKeyguardFadingAwayChanged() {
if (!mKeyguardStateController.isKeyguardFadingAway()) {
if (mBlockUpdates) {
mBlockUpdates = false;
update(true /* force */);
}
}
}
@Override
public void onUnlockedChanged() {
update();
}
};
private final View.AccessibilityDelegate mAccessibilityDelegate =
new View.AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host,
AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
boolean fingerprintRunning =
mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
// Only checking if unlocking with Biometric is allowed (no matter strong or
// non-strong as long as primary auth, i.e. PIN/pattern/password, is not
// required), so it's ok to pass true for isStrongBiometric to
// isUnlockingWithBiometricAllowed() to bypass the check of whether non-strong
// biometric is allowed
boolean unlockingAllowed = mKeyguardUpdateMonitor
.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */);
if (fingerprintRunning && unlockingAllowed) {
AccessibilityNodeInfo.AccessibilityAction unlock =
new AccessibilityNodeInfo.AccessibilityAction(
AccessibilityNodeInfo.ACTION_CLICK,
mResources.getString(
R.string.accessibility_unlock_without_fingerprint));
info.addAction(unlock);
info.setHintText(mResources.getString(
R.string.accessibility_waiting_for_fingerprint));
} else if (getState() == STATE_SCANNING_FACE) {
//Avoid 'button' to be spoken for scanning face
info.setClassName(LockIcon.class.getName());
info.setContentDescription(mResources.getString(
R.string.accessibility_scanning_face));
}
}
};
private int mLastState;
@Inject
public LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger,
KeyguardUpdateMonitor keyguardUpdateMonitor,
LockPatternUtils lockPatternUtils,
ShadeController shadeController,
AccessibilityController accessibilityController,
KeyguardIndicationController keyguardIndicationController,
StatusBarStateController statusBarStateController,
ConfigurationController configurationController,
NotificationWakeUpCoordinator notificationWakeUpCoordinator,
KeyguardBypassController keyguardBypassController,
@Nullable DockManager dockManager,
KeyguardStateController keyguardStateController,
@Main Resources resources,
HeadsUpManagerPhone headsUpManagerPhone) {
mLockscreenGestureLogger = lockscreenGestureLogger;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mShadeController = shadeController;
mAccessibilityController = accessibilityController;
mKeyguardIndicationController = keyguardIndicationController;
mStatusBarStateController = statusBarStateController;
mConfigurationController = configurationController;
mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
mKeyguardBypassController = keyguardBypassController;
mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
mKeyguardStateController = keyguardStateController;
mResources = resources;
mHeadsUpManagerPhone = headsUpManagerPhone;
mKeyguardIndicationController.setLockIconController(this);
}
/**
* Associate the controller with a {@link LockIcon}
*
* TODO: change to an init method and inject the view.
*/
public void attach(LockIcon lockIcon) {
mLockIcon = lockIcon;
mLockIcon.setOnClickListener(this::handleClick);
mLockIcon.setOnLongClickListener(this::handleLongClick);
mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
if (mLockIcon.isAttachedToWindow()) {
mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
}
mLockIcon.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
setStatusBarState(mStatusBarStateController.getState());
}
public LockIcon getView() {
return mLockIcon;
}
/**
* Called whenever the scrims become opaque, transparent or semi-transparent.
*/
public void onScrimVisibilityChanged(Integer scrimsVisible) {
if (mWakeAndUnlockRunning
&& scrimsVisible == ScrimController.TRANSPARENT) {
mWakeAndUnlockRunning = false;
update();
}
}
/**
* Propagate {@link StatusBar} pulsing state.
*/
private void setPulsing(boolean pulsing) {
update();
}
/**
* We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
* icon on top of the black front scrim.
* We also want to halt padlock the animation when we're in face bypass mode or dismissing the
* keyguard with fingerprint.
* @param wakeAndUnlock are we wake and unlocking
* @param isUnlock are we currently unlocking
*/
public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock,
BiometricSourceType type) {
if (wakeAndUnlock) {
mWakeAndUnlockRunning = true;
}
mFingerprintUnlock = type == BiometricSourceType.FINGERPRINT;
if (isUnlock && (mFingerprintUnlock || mKeyguardBypassController.getBypassEnabled())
&& canBlockUpdates()) {
// We don't want the icon to change while we are unlocking
mBlockUpdates = true;
}
update();
}
/**
* When we're launching an affordance, like double pressing power to open camera.
*/
public void onShowingLaunchAffordanceChanged(Boolean showing) {
mShowingLaunchAffordance = showing;
update();
}
/** Sets whether the bouncer is showing. */
public void setBouncerShowingScrimmed(boolean bouncerShowing) {
mBouncerShowingScrimmed = bouncerShowing;
if (mKeyguardBypassController.getBypassEnabled()) {
update();
}
}
/**
* Animate padlock opening when bouncer challenge is solved.
*/
public void onBouncerPreHideAnimation() {
update();
}
/**
* If we're currently presenting an authentication error message.
*/
public void setTransientBiometricsError(boolean transientBiometricsError) {
mTransientBiometricsError = transientBiometricsError;
update();
}
private boolean handleLongClick(View view) {
mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
mKeyguardIndicationController.showTransientIndication(
R.string.keyguard_indication_trust_disabled);
mKeyguardUpdateMonitor.onLockIconPressed();
mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
return true;
}
private void handleClick(View view) {
if (!mAccessibilityController.isAccessibilityEnabled()) {
return;
}
mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
}
private void update() {
update(false /* force */);
}
private void update(boolean force) {
int state = getState();
boolean shouldUpdate = mLastState != state || force;
if (mBlockUpdates && canBlockUpdates()) {
shouldUpdate = false;
}
if (shouldUpdate && mLockIcon != null) {
mLockIcon.update(state, mStatusBarStateController.isPulsing(),
mStatusBarStateController.isDozing(), mKeyguardJustShown);
}
mLastState = state;
mKeyguardJustShown = false;
updateIconVisibility();
updateClickability();
}
private int getState() {
if ((mKeyguardStateController.canDismissLockScreen()
|| !mKeyguardStateController.isShowing()
|| mKeyguardStateController.isKeyguardGoingAway()
|| mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
return STATE_LOCK_OPEN;
} else if (mTransientBiometricsError) {
return STATE_BIOMETRICS_ERROR;
} else if (mKeyguardUpdateMonitor.isFaceDetectionRunning()
&& !mStatusBarStateController.isPulsing()) {
return STATE_SCANNING_FACE;
} else {
return STATE_LOCKED;
}
}
private boolean canBlockUpdates() {
return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
}
private void setDozing(boolean isDozing) {
update();
}
/** Set the StatusBarState. */
private void setStatusBarState(int statusBarState) {
mStatusBarState = statusBarState;
updateIconVisibility();
}
/**
* Update the icon visibility
* @return true if the visibility changed
*/
private boolean updateIconVisibility() {
boolean onAodNotPulsingOrDocked = mStatusBarStateController.isDozing()
&& (!mStatusBarStateController.isPulsing() || mDocked);
boolean invisible = onAodNotPulsingOrDocked || mWakeAndUnlockRunning
|| mShowingLaunchAffordance;
boolean fingerprintOrBypass = mFingerprintUnlock
|| mKeyguardBypassController.getBypassEnabled();
if (fingerprintOrBypass && !mBouncerShowingScrimmed) {
if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
|| mHeadsUpManagerPhone.hasPinnedHeadsUp()
|| mStatusBarState == StatusBarState.KEYGUARD
|| mStatusBarState == StatusBarState.SHADE)
&& !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
invisible = true;
}
}
if (mLockIcon == null) {
return false;
}
return mLockIcon.updateIconVisibility(!invisible);
}
private void updateClickability() {
if (mAccessibilityController == null) {
return;
}
boolean canLock = mKeyguardStateController.isMethodSecure()
&& mKeyguardStateController.canDismissLockScreen();
boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
if (mLockIcon != null) {
mLockIcon.setClickable(clickToUnlock);
mLockIcon.setLongClickable(canLock && !clickToUnlock);
mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
}
}
}