blob: 2f1b589899a0295a9bb0280d968fc29b6c3325f2 [file] [log] [blame]
/*
* Copyright (C) 2014 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;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
import static android.hardware.biometrics.BiometricSourceType.FACE;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
import static com.android.systemui.log.core.LogLevel.ERROR;
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.app.AlarmManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.face.FaceManager;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardIndication;
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.util.IndicationHelper;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.AlarmTimeout;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.wakelock.SettableWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import javax.inject.Inject;
/**
* Controls the indications and error messages shown on the Keyguard
*
* On AoD, only one message shows with the following priorities:
* 1. Biometric
* 2. Transient
* 3. Charging alignment
* 4. Battery information
*
* On the lock screen, message rotate through different message types.
* See {@link KeyguardIndicationRotateTextViewController.IndicationType} for the list of types.
*/
@SysUISingleton
public class KeyguardIndicationController {
public static final String TAG = "KeyguardIndication";
private static final boolean DEBUG_CHARGING_SPEED = false;
private static final int MSG_SHOW_ACTION_TO_UNLOCK = 1;
private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 2;
private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
public static final long DEFAULT_HIDE_DELAY_MS =
3500 + KeyguardIndicationTextView.Y_IN_DURATION;
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
private final KeyguardStateController mKeyguardStateController;
protected final StatusBarStateController mStatusBarStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final AuthController mAuthController;
private final KeyguardLogger mKeyguardLogger;
private final UserTracker mUserTracker;
private final BouncerMessageInteractor mBouncerMessageInteractor;
private ViewGroup mIndicationArea;
private KeyguardIndicationTextView mTopIndicationView;
private KeyguardIndicationTextView mLockScreenIndicationView;
private final IBatteryStats mBatteryInfo;
private final SettableWakeLock mWakeLock;
private final DockManager mDockManager;
private final DevicePolicyManager mDevicePolicyManager;
private final UserManager mUserManager;
protected final @Main DelayableExecutor mExecutor;
protected final @Background DelayableExecutor mBackgroundExecutor;
private final LockPatternUtils mLockPatternUtils;
private final FalsingManager mFalsingManager;
private final KeyguardBypassController mKeyguardBypassController;
private final AccessibilityManager mAccessibilityManager;
private final Handler mHandler;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@VisibleForTesting
public KeyguardIndicationRotateTextViewController mRotateTextViewController;
private BroadcastReceiver mBroadcastReceiver;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private KeyguardInteractor mKeyguardInteractor;
private String mPersistentUnlockMessage;
private String mAlignmentIndication;
private CharSequence mTrustGrantedIndication;
private CharSequence mTransientIndication;
private CharSequence mBiometricMessage;
private CharSequence mBiometricMessageFollowUp;
protected ColorStateList mInitialTextColorState;
private boolean mVisible;
private boolean mOrganizationOwnedDevice;
// these all assume the device is plugged in (wired/wireless/docked) AND chargingOrFull:
private boolean mPowerPluggedIn;
private boolean mPowerPluggedInWired;
private boolean mPowerPluggedInWireless;
private boolean mPowerPluggedInDock;
private boolean mPowerCharged;
private boolean mBatteryDefender;
private boolean mEnableBatteryDefender;
private boolean mIncompatibleCharger;
private int mChargingSpeed;
private int mChargingWattage;
private int mBatteryLevel;
private boolean mBatteryPresent = true;
private long mChargingTimeRemaining;
private String mBiometricErrorMessageToShowOnScreenOn;
private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow;
private final FaceHelpMessageDeferral mFaceAcquiredMessageDeferral;
private boolean mInited;
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
private boolean mDozing;
private boolean mIsActiveDreamLockscreenHosted;
private final ScreenLifecycle mScreenLifecycle;
@VisibleForTesting
final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback =
(Boolean isLockscreenHosted) -> {
if (mIsActiveDreamLockscreenHosted == isLockscreenHosted) {
return;
}
mIsActiveDreamLockscreenHosted = isLockscreenHosted;
updateDeviceEntryIndication(false);
};
private final ScreenLifecycle.Observer mScreenObserver =
new ScreenLifecycle.Observer() {
@Override
public void onScreenTurnedOn() {
mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON);
if (mBiometricErrorMessageToShowOnScreenOn != null) {
String followUpMessage = mFaceLockedOutThisAuthSession
? faceLockedOutFollowupMessage() : null;
showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn, followUpMessage);
// We want to keep this message around in case the screen was off
hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
mBiometricErrorMessageToShowOnScreenOn = null;
}
}
};
private boolean mFaceLockedOutThisAuthSession;
// Use AlarmTimeouts to guarantee that the events are handled even if scheduled and
// triggered while the device is asleep
private final AlarmTimeout mHideTransientMessageHandler;
private final AlarmTimeout mHideBiometricMessageHandler;
private final FeatureFlags mFeatureFlags;
private final IndicationHelper mIndicationHelper;
/**
* Creates a new KeyguardIndicationController and registers callbacks.
*/
@Inject
public KeyguardIndicationController(
Context context,
@Main Looper mainLooper,
WakeLock.Builder wakeLockBuilder,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
DockManager dockManager,
BroadcastDispatcher broadcastDispatcher,
DevicePolicyManager devicePolicyManager,
IBatteryStats iBatteryStats,
UserManager userManager,
@Main DelayableExecutor executor,
@Background DelayableExecutor bgExecutor,
FalsingManager falsingManager,
AuthController authController,
LockPatternUtils lockPatternUtils,
ScreenLifecycle screenLifecycle,
KeyguardBypassController keyguardBypassController,
AccessibilityManager accessibilityManager,
FaceHelpMessageDeferral faceHelpMessageDeferral,
KeyguardLogger keyguardLogger,
AlternateBouncerInteractor alternateBouncerInteractor,
AlarmManager alarmManager,
UserTracker userTracker,
BouncerMessageInteractor bouncerMessageInteractor,
FeatureFlags flags,
IndicationHelper indicationHelper,
KeyguardInteractor keyguardInteractor
) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mDevicePolicyManager = devicePolicyManager;
mKeyguardStateController = keyguardStateController;
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mDockManager = dockManager;
mWakeLock = new SettableWakeLock(
wakeLockBuilder.setTag("Doze:KeyguardIndication").build(), TAG);
mBatteryInfo = iBatteryStats;
mUserManager = userManager;
mExecutor = executor;
mBackgroundExecutor = bgExecutor;
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
mFalsingManager = falsingManager;
mKeyguardBypassController = keyguardBypassController;
mAccessibilityManager = accessibilityManager;
mScreenLifecycle = screenLifecycle;
mKeyguardLogger = keyguardLogger;
mScreenLifecycle.addObserver(mScreenObserver);
mAlternateBouncerInteractor = alternateBouncerInteractor;
mUserTracker = userTracker;
mBouncerMessageInteractor = bouncerMessageInteractor;
mFeatureFlags = flags;
mIndicationHelper = indicationHelper;
mKeyguardInteractor = keyguardInteractor;
mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
int[] msgIds = context.getResources().getIntArray(
com.android.systemui.res.R.array.config_face_help_msgs_when_fingerprint_enrolled);
for (int msgId : msgIds) {
mCoExFaceAcquisitionMsgIdsToShow.add(msgId);
}
mHandler = new Handler(mainLooper) {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
showActionToUnlock();
} else if (msg.what == MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON) {
mBiometricErrorMessageToShowOnScreenOn = null;
}
}
};
mHideTransientMessageHandler = new AlarmTimeout(
alarmManager,
this::hideTransientIndication,
TAG,
mHandler
);
mHideBiometricMessageHandler = new AlarmTimeout(
alarmManager,
this::hideBiometricMessage,
TAG,
mHandler
);
}
/** Call this after construction to finish setting up the instance. */
public void init() {
if (mInited) {
return;
}
mInited = true;
mDockManager.addAlignmentStateListener(
alignState -> mHandler.post(() -> handleAlignStateChanged(alignState)));
mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback());
mStatusBarStateController.addCallback(mStatusBarStateListener);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
mStatusBarStateListener.onDozingChanged(mStatusBarStateController.isDozing());
}
public void setIndicationArea(ViewGroup indicationArea) {
mIndicationArea = indicationArea;
mTopIndicationView = indicationArea.findViewById(R.id.keyguard_indication_text);
mLockScreenIndicationView = indicationArea.findViewById(
R.id.keyguard_indication_text_bottom);
mInitialTextColorState = mTopIndicationView != null
? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
if (mRotateTextViewController != null) {
mRotateTextViewController.destroy();
}
mRotateTextViewController = new KeyguardIndicationRotateTextViewController(
mLockScreenIndicationView,
mExecutor,
mStatusBarStateController,
mKeyguardLogger,
mFeatureFlags
);
updateDeviceEntryIndication(false /* animate */);
updateOrganizedOwnedDevice();
if (mBroadcastReceiver == null) {
// Update the disclosure proactively to avoid IPC on the critical path.
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateOrganizedOwnedDevice();
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, intentFilter);
}
if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
collectFlow(mIndicationArea, mKeyguardInteractor.isActiveDreamLockscreenHosted(),
mIsActiveDreamLockscreenHostedCallback);
}
}
/**
* Cleanup
*/
public void destroy() {
mHandler.removeCallbacksAndMessages(null);
mHideBiometricMessageHandler.cancel();
mHideTransientMessageHandler.cancel();
mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
}
private void handleAlignStateChanged(int alignState) {
String alignmentIndication = "";
if (alignState == DockManager.ALIGN_STATE_POOR) {
alignmentIndication =
mContext.getResources().getString(R.string.dock_alignment_slow_charging);
} else if (alignState == DockManager.ALIGN_STATE_TERRIBLE) {
alignmentIndication =
mContext.getResources().getString(R.string.dock_alignment_not_charging);
}
if (!alignmentIndication.equals(mAlignmentIndication)) {
mAlignmentIndication = alignmentIndication;
updateDeviceEntryIndication(false);
}
}
/**
* Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this
* {@link KeyguardIndicationController}.
*
* <p>Subclasses may override this method to extend or change the callback behavior by extending
* the {@link BaseKeyguardCallback}.
*
* @return A KeyguardUpdateMonitorCallback. Multiple calls to this method <b>must</b> return the
* same instance.
*/
protected KeyguardUpdateMonitorCallback getKeyguardCallback() {
if (mUpdateMonitorCallback == null) {
mUpdateMonitorCallback = new BaseKeyguardCallback();
}
return mUpdateMonitorCallback;
}
private void updateLockScreenIndications(boolean animate, int userId) {
// update transient messages:
updateBiometricMessage();
updateTransient();
// Update persistent messages. The following methods should only be called if we're on the
// lock screen:
updateLockScreenDisclosureMsg();
updateLockScreenOwnerInfo();
updateLockScreenBatteryMsg(animate);
updateLockScreenUserLockedMsg(userId);
updateLockScreenTrustMsg(userId, getTrustGrantedIndication(), getTrustManagedIndication());
updateLockScreenAlignmentMsg();
updateLockScreenLogoutView();
updateLockScreenPersistentUnlockMsg();
}
private void updateOrganizedOwnedDevice() {
// avoid calling this method since it has an IPC
mOrganizationOwnedDevice = whitelistIpcs(this::isOrganizationOwnedDevice);
updateDeviceEntryIndication(false);
}
private void updateLockScreenDisclosureMsg() {
if (mOrganizationOwnedDevice) {
mBackgroundExecutor.execute(() -> {
final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
final CharSequence disclosure = getDisclosureText(organizationName);
mExecutor.execute(() -> {
if (mKeyguardStateController.isShowing()) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_DISCLOSURE,
new KeyguardIndication.Builder()
.setMessage(disclosure)
.setTextColor(mInitialTextColorState)
.build(),
/* updateImmediately */ false);
}
});
});
} else {
mRotateTextViewController.hideIndication(INDICATION_TYPE_DISCLOSURE);
}
}
private CharSequence getDisclosureText(@Nullable CharSequence organizationName) {
final Resources packageResources = mContext.getResources();
// TODO(b/259908270): remove and inline
boolean isFinanced;
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
DevicePolicyManager.ADD_ISFINANCED_DEVICE_FLAG,
DevicePolicyManager.ADD_ISFINANCED_FEVICE_DEFAULT)) {
isFinanced = mDevicePolicyManager.isFinancedDevice();
} else {
isFinanced = mDevicePolicyManager.isDeviceManaged()
&& mDevicePolicyManager.getDeviceOwnerType(
mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser())
== DEVICE_OWNER_TYPE_FINANCED;
}
if (organizationName == null) {
return mDevicePolicyManager.getResources().getString(
KEYGUARD_MANAGEMENT_DISCLOSURE,
() -> packageResources.getString(R.string.do_disclosure_generic));
} else if (isFinanced) {
return packageResources.getString(R.string.do_financed_disclosure_with_name,
organizationName);
} else {
return mDevicePolicyManager.getResources().getString(
KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE,
() -> packageResources.getString(
R.string.do_disclosure_with_name, organizationName),
organizationName);
}
}
private int getCurrentUser() {
return mUserTracker.getUserId();
}
private void updateLockScreenOwnerInfo() {
// Check device owner info on a bg thread.
// It makes multiple IPCs that could block the thread it's run on.
mBackgroundExecutor.execute(() -> {
String info = mLockPatternUtils.getDeviceOwnerInfo();
if (info == null) {
// Use the current user owner information if enabled.
final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
getCurrentUser());
if (ownerInfoEnabled) {
info = mLockPatternUtils.getOwnerInfo(getCurrentUser());
}
}
// Update the UI on the main thread.
final String finalInfo = info;
mExecutor.execute(() -> {
if (!TextUtils.isEmpty(finalInfo) && mKeyguardStateController.isShowing()) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_OWNER_INFO,
new KeyguardIndication.Builder()
.setMessage(finalInfo)
.setTextColor(mInitialTextColorState)
.build(),
false);
} else {
mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO);
}
});
});
}
private void updateLockScreenBatteryMsg(boolean animate) {
if (mPowerPluggedIn || mEnableBatteryDefender) {
String powerIndication = computePowerIndication();
if (DEBUG_CHARGING_SPEED) {
powerIndication += ", " + (mChargingWattage / 1000) + " mW";
}
mKeyguardLogger.logUpdateBatteryIndication(powerIndication, mPowerPluggedIn);
mRotateTextViewController.updateIndication(
INDICATION_TYPE_BATTERY,
new KeyguardIndication.Builder()
.setMessage(powerIndication)
.setTextColor(mInitialTextColorState)
.build(),
animate);
} else {
mKeyguardLogger.log(TAG, LogLevel.DEBUG, "hide battery indication");
// don't show the charging information if device isn't plugged in
mRotateTextViewController.hideIndication(INDICATION_TYPE_BATTERY);
}
}
private void updateLockScreenUserLockedMsg(int userId) {
if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)
|| mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_USER_LOCKED,
new KeyguardIndication.Builder()
.setMessage(mContext.getResources().getText(
com.android.internal.R.string.lockscreen_storage_locked))
.setTextColor(mInitialTextColorState)
.build(),
false);
} else {
mRotateTextViewController.hideIndication(INDICATION_TYPE_USER_LOCKED);
}
}
private void updateBiometricMessage() {
if (mDozing) {
updateDeviceEntryIndication(false);
return;
}
if (!TextUtils.isEmpty(mBiometricMessage)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_BIOMETRIC_MESSAGE,
new KeyguardIndication.Builder()
.setMessage(mBiometricMessage)
.setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
.setTextColor(mInitialTextColorState)
.build(),
true
);
} else {
mRotateTextViewController.hideIndication(
INDICATION_TYPE_BIOMETRIC_MESSAGE);
}
if (!TextUtils.isEmpty(mBiometricMessageFollowUp)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
new KeyguardIndication.Builder()
.setMessage(mBiometricMessageFollowUp)
.setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
.setTextColor(mInitialTextColorState)
.build(),
true
);
} else {
mRotateTextViewController.hideIndication(
INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
}
}
private void updateTransient() {
if (mDozing) {
updateDeviceEntryIndication(false);
return;
}
if (!TextUtils.isEmpty(mTransientIndication)) {
mRotateTextViewController.showTransient(mTransientIndication);
} else {
mRotateTextViewController.hideTransient();
}
}
private void updateLockScreenTrustMsg(int userId, CharSequence trustGrantedIndication,
CharSequence trustManagedIndication) {
final boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(userId);
if (!TextUtils.isEmpty(trustGrantedIndication) && userHasTrust) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_TRUST,
new KeyguardIndication.Builder()
.setMessage(trustGrantedIndication)
.setTextColor(mInitialTextColorState)
.build(),
true);
hideBiometricMessage();
} else if (!TextUtils.isEmpty(trustManagedIndication)
&& mKeyguardUpdateMonitor.getUserTrustIsManaged(userId)
&& !userHasTrust) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_TRUST,
new KeyguardIndication.Builder()
.setMessage(trustManagedIndication)
.setTextColor(mInitialTextColorState)
.build(),
false);
} else {
mRotateTextViewController.hideIndication(INDICATION_TYPE_TRUST);
}
}
private void updateLockScreenAlignmentMsg() {
if (!TextUtils.isEmpty(mAlignmentIndication)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_ALIGNMENT,
new KeyguardIndication.Builder()
.setMessage(mAlignmentIndication)
.setTextColor(ColorStateList.valueOf(
mContext.getColor(R.color.misalignment_text_color)))
.build(),
true);
} else {
mRotateTextViewController.hideIndication(INDICATION_TYPE_ALIGNMENT);
}
}
private void updateLockScreenPersistentUnlockMsg() {
if (!TextUtils.isEmpty(mPersistentUnlockMessage)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
new KeyguardIndication.Builder()
.setMessage(mPersistentUnlockMessage)
.setTextColor(mInitialTextColorState)
.build(),
true);
} else {
mRotateTextViewController.hideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
}
}
private void updateLockScreenLogoutView() {
final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
&& getCurrentUser() != UserHandle.USER_SYSTEM;
if (shouldShowLogout) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_LOGOUT,
new KeyguardIndication.Builder()
.setMessage(mContext.getResources().getString(
com.android.internal.R.string.global_action_logout))
.setTextColor(Utils.getColorAttr(
mContext, com.android.internal.R.attr.textColorOnAccent))
.setBackground(mContext.getDrawable(
com.android.systemui.res.R.drawable.logout_button_background))
.setClickListener((view) -> {
if (mFalsingManager.isFalseTap(LOW_PENALTY)) {
return;
}
mDevicePolicyManager.logoutUser();
})
.build(),
false);
} else {
mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT);
}
}
private boolean isOrganizationOwnedDevice() {
return mDevicePolicyManager.isDeviceManaged()
|| mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
}
@Nullable
private CharSequence getOrganizationOwnedDeviceOrganizationName() {
if (mDevicePolicyManager.isDeviceManaged()) {
return mDevicePolicyManager.getDeviceOwnerOrganizationName();
} else if (mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) {
return getWorkProfileOrganizationName();
}
return null;
}
private CharSequence getWorkProfileOrganizationName() {
final int profileId = getWorkProfileUserId(UserHandle.myUserId());
if (profileId == UserHandle.USER_NULL) {
return null;
}
return mDevicePolicyManager.getOrganizationNameForUser(profileId);
}
private int getWorkProfileUserId(int userId) {
for (final UserInfo userInfo : mUserManager.getProfiles(userId)) {
if (userInfo.isManagedProfile()) {
return userInfo.id;
}
}
return UserHandle.USER_NULL;
}
/**
* Sets the visibility of keyguard bottom area, and if the indications are updatable.
*
* @param visible true to make the area visible and update the indication, false otherwise.
*/
public void setVisible(boolean visible) {
mVisible = visible;
mIndicationArea.setVisibility(visible ? VISIBLE : GONE);
if (visible) {
// If this is called after an error message was already shown, we should not clear it.
// Otherwise the error message won't be shown
if (!mHideTransientMessageHandler.isScheduled()) {
hideTransientIndication();
}
updateDeviceEntryIndication(false);
} else {
// If we unlock and return to keyguard quickly, previous error should not be shown
hideTransientIndication();
}
}
private void setPersistentUnlockMessage(String persistentUnlockMessage) {
mPersistentUnlockMessage = persistentUnlockMessage;
updateDeviceEntryIndication(false);
}
/**
* Returns the indication text indicating that trust has been granted.
*
* @return an empty string if a trust indication text should not be shown.
*/
@VisibleForTesting
String getTrustGrantedIndication() {
return mTrustGrantedIndication == null
? mContext.getString(R.string.keyguard_indication_trust_unlocked)
: mTrustGrantedIndication.toString();
}
/**
* Sets if the device is plugged in
*/
@VisibleForTesting
void setPowerPluggedIn(boolean plugged) {
mPowerPluggedIn = plugged;
}
/**
* Returns the indication text indicating that trust is currently being managed.
*
* @return {@code null} or an empty string if a trust managed text should not be shown.
*/
private String getTrustManagedIndication() {
return null;
}
/**
* Hides transient indication in {@param delayMs}.
*/
public void hideTransientIndicationDelayed(long delayMs) {
mHideTransientMessageHandler.schedule(delayMs, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
}
/**
* Hides biometric indication in {@param delayMs}.
*/
public void hideBiometricMessageDelayed(long delayMs) {
mHideBiometricMessageHandler.schedule(delayMs, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
}
/**
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
public void showTransientIndication(int transientIndication) {
showTransientIndication(mContext.getResources().getString(transientIndication));
}
/**
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
private void showTransientIndication(CharSequence transientIndication) {
mTransientIndication = transientIndication;
hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
updateTransient();
}
private void showBiometricMessage(CharSequence biometricMessage) {
showBiometricMessage(biometricMessage, null);
}
/**
* Shows {@param biometricMessage} and {@param biometricMessageFollowUp}
* until they are hidden by {@link #hideBiometricMessage}. Messages are rotated through
* by {@link KeyguardIndicationRotateTextViewController}, see class for rotating message
* logic.
*/
private void showBiometricMessage(CharSequence biometricMessage,
@Nullable CharSequence biometricMessageFollowUp) {
if (TextUtils.equals(biometricMessage, mBiometricMessage)
&& TextUtils.equals(biometricMessageFollowUp, mBiometricMessageFollowUp)) {
return;
}
mBiometricMessage = biometricMessage;
mBiometricMessageFollowUp = biometricMessageFollowUp;
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
hideBiometricMessageDelayed(
!TextUtils.isEmpty(mBiometricMessage)
&& !TextUtils.isEmpty(mBiometricMessageFollowUp)
? IMPORTANT_MSG_MIN_DURATION * 2
: DEFAULT_HIDE_DELAY_MS
);
updateBiometricMessage();
}
private void hideBiometricMessage() {
if (mBiometricMessage != null || mBiometricMessageFollowUp != null) {
mBiometricMessage = null;
mBiometricMessageFollowUp = null;
mHideBiometricMessageHandler.cancel();
updateBiometricMessage();
}
}
/**
* Hides transient indication.
*/
public void hideTransientIndication() {
if (mTransientIndication != null) {
mTransientIndication = null;
mHideTransientMessageHandler.cancel();
updateTransient();
}
}
/**
* Updates message shown to the user. If the device is dozing, a single message with the highest
* precedence is shown. If the device is not dozing (on the lock screen), then several messages
* may continuously be cycled through.
*/
protected final void updateDeviceEntryIndication(boolean animate) {
mKeyguardLogger.logUpdateDeviceEntryIndication(animate, mVisible, mDozing);
if (!mVisible) {
return;
}
// Device is dreaming and the dream is hosted in lockscreen
if (mIsActiveDreamLockscreenHosted) {
mIndicationArea.setVisibility(GONE);
return;
}
// A few places might need to hide the indication, so always start by making it visible
mIndicationArea.setVisibility(VISIBLE);
// Walk down a precedence-ordered list of what indication
// should be shown based on device state
if (mDozing) {
boolean useMisalignmentColor = false;
mLockScreenIndicationView.setVisibility(View.GONE);
mTopIndicationView.setVisibility(VISIBLE);
CharSequence newIndication;
if (!TextUtils.isEmpty(mBiometricMessage)) {
newIndication = mBiometricMessage; // note: doesn't show mBiometricMessageFollowUp
} else if (!TextUtils.isEmpty(mTransientIndication)) {
newIndication = mTransientIndication;
} else if (!mBatteryPresent) {
// If there is no battery detected, hide the indication and bail
mIndicationArea.setVisibility(GONE);
return;
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
useMisalignmentColor = true;
newIndication = mAlignmentIndication;
} else if (mPowerPluggedIn || mEnableBatteryDefender) {
newIndication = computePowerIndication();
} else {
newIndication = NumberFormat.getPercentInstance()
.format(mBatteryLevel / 100f);
}
if (!TextUtils.equals(mTopIndicationView.getText(), newIndication)) {
mWakeLock.setAcquired(true);
mTopIndicationView.switchIndication(newIndication,
new KeyguardIndication.Builder()
.setMessage(newIndication)
.setTextColor(ColorStateList.valueOf(
useMisalignmentColor
? mContext.getColor(R.color.misalignment_text_color)
: Color.WHITE))
.build(),
true, () -> mWakeLock.setAcquired(false));
}
return;
}
// LOCK SCREEN
mTopIndicationView.setVisibility(GONE);
mTopIndicationView.setText(null);
mLockScreenIndicationView.setVisibility(View.VISIBLE);
updateLockScreenIndications(animate, getCurrentUser());
}
/**
* Assumption: device is charging
*/
protected String computePowerIndication() {
int chargingId;
if (mBatteryDefender) {
chargingId = R.string.keyguard_plugged_in_charging_limited;
String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
return mContext.getResources().getString(chargingId, percentage);
} else if (mPowerPluggedIn && mIncompatibleCharger) {
chargingId = R.string.keyguard_plugged_in_incompatible_charger;
String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
return mContext.getResources().getString(chargingId, percentage);
} else if (mPowerCharged) {
return mContext.getResources().getString(R.string.keyguard_charged);
}
final boolean hasChargingTime = mChargingTimeRemaining > 0;
if (mPowerPluggedInWired) {
switch (mChargingSpeed) {
case BatteryStatus.CHARGING_FAST:
chargingId = hasChargingTime
? R.string.keyguard_indication_charging_time_fast
: R.string.keyguard_plugged_in_charging_fast;
break;
case BatteryStatus.CHARGING_SLOWLY:
chargingId = hasChargingTime
? R.string.keyguard_indication_charging_time_slowly
: R.string.keyguard_plugged_in_charging_slowly;
break;
default:
chargingId = hasChargingTime
? R.string.keyguard_indication_charging_time
: R.string.keyguard_plugged_in;
break;
}
} else if (mPowerPluggedInWireless) {
chargingId = hasChargingTime
? R.string.keyguard_indication_charging_time_wireless
: R.string.keyguard_plugged_in_wireless;
} else if (mPowerPluggedInDock) {
chargingId = hasChargingTime
? R.string.keyguard_indication_charging_time_dock
: R.string.keyguard_plugged_in_dock;
} else {
chargingId = hasChargingTime
? R.string.keyguard_indication_charging_time
: R.string.keyguard_plugged_in;
}
String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
if (hasChargingTime) {
String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
mContext, mChargingTimeRemaining);
return mContext.getResources().getString(chargingId, chargingTimeFormatted,
percentage);
} else {
return mContext.getResources().getString(chargingId, percentage);
}
}
public void setStatusBarKeyguardViewManager(
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
}
/**
* Show message on the keyguard for how the user can unlock/enter their device.
*/
public void showActionToUnlock() {
if (mDozing
&& !mKeyguardUpdateMonitor.getUserCanSkipBouncer(
getCurrentUser())) {
return;
}
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
if (mAlternateBouncerInteractor.isVisibleState()) {
return; // udfps affordance is highlighted, no need to show action to unlock
} else if (mKeyguardUpdateMonitor.isFaceEnrolled()
&& !mKeyguardUpdateMonitor.getIsFaceAuthenticated()) {
String message;
if (mAccessibilityManager.isEnabled()
|| mAccessibilityManager.isTouchExplorationEnabled()) {
message = mContext.getString(R.string.accesssibility_keyguard_retry);
} else {
message = mContext.getString(R.string.keyguard_retry);
}
mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState);
}
} else {
final boolean canSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(
getCurrentUser());
if (canSkipBouncer) {
final boolean faceAuthenticated = mKeyguardUpdateMonitor.getIsFaceAuthenticated();
final boolean udfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported();
final boolean a11yEnabled = mAccessibilityManager.isEnabled()
|| mAccessibilityManager.isTouchExplorationEnabled();
if (udfpsSupported && faceAuthenticated) { // co-ex
if (a11yEnabled) {
showBiometricMessage(
mContext.getString(R.string.keyguard_face_successful_unlock),
mContext.getString(R.string.keyguard_unlock)
);
} else {
showBiometricMessage(
mContext.getString(R.string.keyguard_face_successful_unlock),
mContext.getString(R.string.keyguard_unlock_press)
);
}
} else if (faceAuthenticated) { // face-only
showBiometricMessage(
mContext.getString(R.string.keyguard_face_successful_unlock),
mContext.getString(R.string.keyguard_unlock)
);
} else if (udfpsSupported) { // udfps-only
if (a11yEnabled) {
showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
} else {
showBiometricMessage(mContext.getString(
R.string.keyguard_unlock_press));
}
} else { // no security or unlocked by a trust agent
showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
}
} else {
// suggest swiping up for the primary authentication bouncer
showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
}
}
}
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardIndicationController:");
pw.println(" mInitialTextColorState: " + mInitialTextColorState);
pw.println(" mPowerPluggedInWired: " + mPowerPluggedInWired);
pw.println(" mPowerPluggedIn: " + mPowerPluggedIn);
pw.println(" mPowerCharged: " + mPowerCharged);
pw.println(" mChargingSpeed: " + mChargingSpeed);
pw.println(" mChargingWattage: " + mChargingWattage);
pw.println(" mMessageToShowOnScreenOn: " + mBiometricErrorMessageToShowOnScreenOn);
pw.println(" mDozing: " + mDozing);
pw.println(" mTransientIndication: " + mTransientIndication);
pw.println(" mBiometricMessage: " + mBiometricMessage);
pw.println(" mBiometricMessageFollowUp: " + mBiometricMessageFollowUp);
pw.println(" mBatteryLevel: " + mBatteryLevel);
pw.println(" mBatteryPresent: " + mBatteryPresent);
pw.println(" mIsActiveDreamLockscreenHosted: " + mIsActiveDreamLockscreenHosted);
pw.println(" AOD text: " + (
mTopIndicationView == null ? null : mTopIndicationView.getText()));
pw.println(" computePowerIndication(): " + computePowerIndication());
pw.println(" trustGrantedIndication: " + getTrustGrantedIndication());
pw.println(" mCoExFaceHelpMsgIdsToShow=" + mCoExFaceAcquisitionMsgIdsToShow);
mRotateTextViewController.dump(pw, args);
}
protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
@Override
public void onTimeChanged() {
if (mVisible) {
updateDeviceEntryIndication(false /* animate */);
}
}
/**
* KeyguardUpdateMonitor only sends "interesting" battery updates
* {@link KeyguardUpdateMonitor#isBatteryUpdateInteresting}.
* Therefore, make sure to always check plugged in state along with any charging status
* change, or else we could end up with stale state.
*/
@Override
public void onRefreshBatteryInfo(BatteryStatus status) {
boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
|| status.isCharged();
boolean wasPluggedIn = mPowerPluggedIn;
mPowerPluggedInWired = status.isPluggedInWired() && isChargingOrFull;
mPowerPluggedInWireless = status.isPluggedInWireless() && isChargingOrFull;
mPowerPluggedInDock = status.isPluggedInDock() && isChargingOrFull;
mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull;
mPowerCharged = status.isCharged();
mChargingWattage = status.maxChargingWattage;
mChargingSpeed = status.getChargingSpeed(mContext);
mBatteryLevel = status.level;
mBatteryPresent = status.present;
mBatteryDefender = status.isBatteryDefender();
// when the battery is overheated, device doesn't charge so only guard on pluggedIn:
mEnableBatteryDefender = mBatteryDefender && status.isPluggedIn();
mIncompatibleCharger = status.incompatibleCharger.orElse(false);
try {
mChargingTimeRemaining = mPowerPluggedIn
? mBatteryInfo.computeChargeTimeRemaining() : -1;
} catch (RemoteException e) {
mKeyguardLogger.log(TAG, ERROR, "Error calling IBatteryStats", e);
mChargingTimeRemaining = -1;
}
mKeyguardLogger.logRefreshBatteryInfo(isChargingOrFull, mPowerPluggedIn, mBatteryLevel,
mBatteryDefender);
updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
}
@Override
public void onBiometricAcquired(BiometricSourceType biometricSourceType, int acquireInfo) {
if (biometricSourceType == FACE) {
mFaceAcquiredMessageDeferral.processFrame(acquireInfo);
}
}
@Override
public void onBiometricHelp(int msgId, String helpString,
BiometricSourceType biometricSourceType) {
if (biometricSourceType == FACE) {
mFaceAcquiredMessageDeferral.updateMessage(msgId, helpString);
if (mFaceAcquiredMessageDeferral.shouldDefer(msgId)) {
return;
}
}
final boolean faceAuthUnavailable = biometricSourceType == FACE
&& msgId == BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
if (isPrimaryAuthRequired()
&& !faceAuthUnavailable) {
return;
}
final boolean faceAuthSoftError = biometricSourceType == FACE
&& msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
&& msgId != BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
final boolean faceAuthFailed = biometricSourceType == FACE
&& msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed
final boolean fpAuthFailed = biometricSourceType == FINGERPRINT
&& msgId == BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; // ran matcher & failed
final boolean isUnlockWithFingerprintPossible = canUnlockWithFingerprint();
final boolean isCoExFaceAcquisitionMessage =
faceAuthSoftError && isUnlockWithFingerprintPossible;
if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) {
mKeyguardLogger.logBiometricMessage(
"skipped showing help message due to co-ex logic",
msgId,
helpString);
} else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
if (biometricSourceType == FINGERPRINT && !fpAuthFailed) {
mBouncerMessageInteractor.setFingerprintAcquisitionMessage(helpString);
} else if (faceAuthSoftError) {
mBouncerMessageInteractor.setFaceAcquisitionMessage(helpString);
}
mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
mInitialTextColorState);
} else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
if (isCoExFaceAcquisitionMessage && msgId == FACE_ACQUIRED_TOO_DARK) {
showBiometricMessage(
helpString,
mContext.getString(R.string.keyguard_suggest_fingerprint)
);
} else if (faceAuthFailed && isUnlockWithFingerprintPossible) {
showBiometricMessage(
mContext.getString(R.string.keyguard_face_failed),
mContext.getString(R.string.keyguard_suggest_fingerprint)
);
} else if (fpAuthFailed
&& mKeyguardUpdateMonitor.getUserUnlockedWithFace(getCurrentUser())) {
// face had already previously unlocked the device, so instead of showing a
// fingerprint error, tell them they have already unlocked with face auth
// and how to enter their device
showBiometricMessage(
mContext.getString(R.string.keyguard_face_successful_unlock),
mContext.getString(R.string.keyguard_unlock)
);
} else if (fpAuthFailed
&& mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())) {
showBiometricMessage(
getTrustGrantedIndication(),
mContext.getString(R.string.keyguard_unlock)
);
} else if (faceAuthUnavailable) {
showBiometricMessage(
helpString,
isUnlockWithFingerprintPossible
? mContext.getString(R.string.keyguard_suggest_fingerprint)
: mContext.getString(R.string.keyguard_unlock)
);
} else {
showBiometricMessage(helpString);
}
} else if (faceAuthFailed) {
// show action to unlock
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK),
TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
} else {
mBiometricErrorMessageToShowOnScreenOn = helpString;
mHandler.sendMessageDelayed(
mHandler.obtainMessage(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON),
1000);
}
}
@Override
public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
if (biometricSourceType == FACE) {
mFaceAcquiredMessageDeferral.reset();
}
}
@Override
public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
if (biometricSourceType == FACE && !mKeyguardUpdateMonitor.isFaceLockedOut()) {
mFaceLockedOutThisAuthSession = false;
} else if (biometricSourceType == FINGERPRINT) {
setPersistentUnlockMessage(mKeyguardUpdateMonitor.isFingerprintLockedOut()
? mContext.getString(R.string.keyguard_unlock) : "");
}
}
@Override
public void onBiometricError(int msgId, String errString,
BiometricSourceType biometricSourceType) {
if (biometricSourceType == FACE) {
onFaceAuthError(msgId, errString);
} else if (biometricSourceType == FINGERPRINT) {
onFingerprintAuthError(msgId, errString);
}
}
private void onFaceAuthError(int msgId, String errString) {
CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
mFaceAcquiredMessageDeferral.reset();
if (mIndicationHelper.shouldSuppressErrorMsg(FACE, msgId)) {
mKeyguardLogger.logBiometricMessage("KIC suppressingFaceError", msgId, errString);
return;
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
handleFaceAuthTimeoutError(deferredFaceMessage);
} else if (mIndicationHelper.isFaceLockoutErrorMsg(msgId)) {
handleFaceLockoutError(errString);
} else {
showErrorMessageNowOrLater(errString, null);
}
}
private void onFingerprintAuthError(int msgId, String errString) {
if (mIndicationHelper.shouldSuppressErrorMsg(FINGERPRINT, msgId)) {
mKeyguardLogger.logBiometricMessage("KIC suppressingFingerprintError",
msgId,
errString);
} else {
showErrorMessageNowOrLater(errString, null);
}
}
@Override
public void onTrustChanged(int userId) {
if (!isCurrentUser(userId)) return;
updateDeviceEntryIndication(false);
}
@Override
public void onTrustGrantedForCurrentUser(
boolean dismissKeyguard,
boolean newlyUnlocked,
@NonNull TrustGrantFlags flags,
@Nullable String message
) {
showTrustGrantedMessage(dismissKeyguard, message);
}
@Override
public void onTrustAgentErrorMessage(CharSequence message) {
showBiometricMessage(message);
}
@Override
public void onBiometricRunningStateChanged(boolean running,
BiometricSourceType biometricSourceType) {
if (running && biometricSourceType == FACE) {
// Let's hide any previous messages when authentication starts, otherwise
// multiple auth attempts would overlap.
hideBiometricMessage();
mBiometricErrorMessageToShowOnScreenOn = null;
}
}
@Override
public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric);
hideBiometricMessage();
if (biometricSourceType == FACE) {
mFaceAcquiredMessageDeferral.reset();
if (!mKeyguardBypassController.canBypass()) {
showActionToUnlock();
}
}
}
@Override
public void onUserSwitchComplete(int userId) {
if (mVisible) {
updateDeviceEntryIndication(false);
}
}
@Override
public void onUserUnlocked() {
if (mVisible) {
updateDeviceEntryIndication(false);
}
}
@Override
public void onLogoutEnabledChanged() {
if (mVisible) {
updateDeviceEntryIndication(false);
}
}
@Override
public void onRequireUnlockForNfc() {
showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc));
hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
}
}
private boolean isPrimaryAuthRequired() {
// 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 required), so it's ok to
// pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
// check of whether non-strong biometric is allowed since strong biometrics can still be
// used.
return !mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
true /* isStrongBiometric */);
}
protected boolean isPluggedInAndCharging() {
return mPowerPluggedIn;
}
private boolean isCurrentUser(int userId) {
return getCurrentUser() == userId;
}
protected void showTrustGrantedMessage(boolean dismissKeyguard, @Nullable String message) {
mTrustGrantedIndication = message;
updateDeviceEntryIndication(false);
}
private void handleFaceLockoutError(String errString) {
String followupMessage = faceLockedOutFollowupMessage();
// Lockout error can happen multiple times in a session because we trigger face auth
// even when it is locked out so that the user is aware that face unlock would have
// triggered but didn't because it is locked out.
// On first lockout we show the error message from FaceManager, which tells the user they
// had too many unsuccessful attempts.
if (!mFaceLockedOutThisAuthSession) {
mFaceLockedOutThisAuthSession = true;
showErrorMessageNowOrLater(errString, followupMessage);
} else if (!mAuthController.isUdfpsFingerDown()) {
// On subsequent lockouts, we show a more generic locked out message.
showErrorMessageNowOrLater(
mContext.getString(R.string.keyguard_face_unlock_unavailable),
followupMessage);
}
}
private String faceLockedOutFollowupMessage() {
int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
: R.string.keyguard_unlock;
return mContext.getString(followupMsgId);
}
private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
mKeyguardLogger.logBiometricMessage("deferred message after face auth timeout",
null, String.valueOf(deferredFaceMessage));
if (canUnlockWithFingerprint()) {
// Co-ex: show deferred message OR nothing
// if we're on the lock screen (bouncer isn't showing), show the deferred msg
if (deferredFaceMessage != null
&& !mStatusBarKeyguardViewManager.isBouncerShowing()) {
showBiometricMessage(
deferredFaceMessage,
mContext.getString(R.string.keyguard_suggest_fingerprint)
);
} else {
// otherwise, don't show any message
mKeyguardLogger.logBiometricMessage(
"skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
}
} else if (deferredFaceMessage != null) {
// Face-only: The face timeout message is not very actionable, let's ask the
// user to manually retry.
showBiometricMessage(
deferredFaceMessage,
mContext.getString(R.string.keyguard_unlock)
);
} else {
// Face-only
// suggest swiping up to unlock (try face auth again or swipe up to bouncer)
showActionToUnlock();
}
}
private boolean canUnlockWithFingerprint() {
return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
}
private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
} else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
showBiometricMessage(errString, followUpMsg);
} else {
mBiometricErrorMessageToShowOnScreenOn = errString;
}
}
private final StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onStateChanged(int newState) {
setVisible(newState == StatusBarState.KEYGUARD);
}
@Override
public void onDozingChanged(boolean dozing) {
if (mDozing == dozing) {
return;
}
mDozing = dozing;
if (mDozing) {
hideBiometricMessage();
}
updateDeviceEntryIndication(false);
}
};
private final KeyguardStateController.Callback mKeyguardStateCallback =
new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
updateDeviceEntryIndication(false);
}
@Override
public void onKeyguardShowingChanged() {
// All transient messages are gone the next time keyguard is shown
if (!mKeyguardStateController.isShowing()) {
mKeyguardLogger.log(TAG, LogLevel.DEBUG, "clear messages");
mTopIndicationView.clearMessages();
mRotateTextViewController.clearMessages();
} else {
updateDeviceEntryIndication(false);
}
}
};
}