diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index a424674ed252c..d73d9cdb7d40f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -19,6 +19,7 @@ package com.android.systemui.keyguard; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.graphics.Color; +import android.os.SystemClock; import android.text.TextUtils; import androidx.annotation.IntDef; @@ -40,13 +41,24 @@ import java.util.List; import java.util.Map; /** - * Rotates through messages to show on the keyguard bottom area on the lock screen - * NOTE: This controller should not be used on AoD to avoid waking up the AP too often. + * Animates through messages to show on the keyguard bottom area on the lock screen. + * Utilizes a {@link KeyguardIndicationTextView} for animations. This class handles the rotating + * nature of the messages including: + * - ensuring a message is shown for its minimum amount of time. Minimum time is determined by + * {@link KeyguardIndication#getMinVisibilityMillis()} + * - showing the next message after a default of 3.5 seconds before animating to the next + * - statically showing a single message if there is only one message to show + * - showing certain messages immediately, assuming te current message has been shown for + * at least {@link KeyguardIndication#getMinVisibilityMillis()}. For example, transient and + * biometric messages are meant to be shown immediately. + * - ending animations when dozing begins, and resuming when dozing ends. Rotating messages on + * AoD is undesirable since it wakes up the AP too often. */ public class KeyguardIndicationRotateTextViewController extends ViewController implements Dumpable { public static String TAG = "KgIndicationRotatingCtrl"; private static final long DEFAULT_INDICATION_SHOW_LENGTH = 3500; // milliseconds + public static final long IMPORTANT_MSG_MIN_DURATION = 2000L + 600L; // 2000ms + [Y in duration] private final StatusBarStateController mStatusBarStateController; private final float mMaxAlpha; @@ -62,6 +74,8 @@ public class KeyguardIndicationRotateTextViewController extends // List of indication types to show. The next indication to show is always at index 0 private final List mIndicationQueue = new LinkedList<>(); private @IndicationType int mCurrIndicationType = INDICATION_TYPE_NONE; + private CharSequence mCurrMessage; + private long mLastIndicationSwitch; private boolean mIsDozing; @@ -94,17 +108,19 @@ public class KeyguardIndicationRotateTextViewController extends * Update the indication type with the given String. * @param type of indication * @param newIndication message to associate with this indication type - * @param showImmediately if true: shows this indication message immediately. Else, the text - * associated with this type is updated and will show when its turn in - * the IndicationQueue comes around. + * @param showAsap if true: shows this indication message as soon as possible. If false, + * the text associated with this type is updated and will show when its turn + * in the IndicationQueue comes around. */ public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, - boolean updateImmediately) { + boolean showAsap) { if (type == INDICATION_TYPE_REVERSE_CHARGING) { // temporarily don't show here, instead use AmbientContainer b/181049781 return; } - final boolean hasPreviousIndication = mIndicationMessages.get(type) != null; + long minShowDuration = getMinVisibilityMillis(mIndicationMessages.get(mCurrIndicationType)); + final boolean hasPreviousIndication = mIndicationMessages.get(type) != null + && !TextUtils.isEmpty(mIndicationMessages.get(type).getMessage()); final boolean hasNewIndication = newIndication != null; if (!hasNewIndication) { mIndicationMessages.remove(type); @@ -121,25 +137,46 @@ public class KeyguardIndicationRotateTextViewController extends return; } - final boolean showNow = updateImmediately - || mCurrIndicationType == INDICATION_TYPE_NONE - || mCurrIndicationType == type; + long currTime = SystemClock.uptimeMillis(); + long timeSinceLastIndicationSwitch = currTime - mLastIndicationSwitch; + boolean currMsgShownForMinTime = timeSinceLastIndicationSwitch >= minShowDuration; if (hasNewIndication) { - if (showNow) { + if (mCurrIndicationType == INDICATION_TYPE_NONE || mCurrIndicationType == type) { showIndication(type); + } else if (showAsap) { + if (currMsgShownForMinTime) { + showIndication(type); + } else { + mIndicationQueue.removeIf(x -> x == type); + mIndicationQueue.add(0 /* index */, type /* type */); + scheduleShowNextIndication(minShowDuration - timeSinceLastIndicationSwitch); + } } else if (!isNextIndicationScheduled()) { - scheduleShowNextIndication(); + long nextShowTime = Math.max( + getMinVisibilityMillis(mIndicationMessages.get(type)), + DEFAULT_INDICATION_SHOW_LENGTH); + if (timeSinceLastIndicationSwitch >= nextShowTime) { + showIndication(type); + } else { + scheduleShowNextIndication( + nextShowTime - timeSinceLastIndicationSwitch); + } } return; } + // current indication is updated to empty if (mCurrIndicationType == type && !hasNewIndication - && updateImmediately) { - if (mShowNextIndicationRunnable != null) { - mShowNextIndicationRunnable.runImmediately(); + && showAsap) { + if (currMsgShownForMinTime) { + if (mShowNextIndicationRunnable != null) { + mShowNextIndicationRunnable.runImmediately(); + } else { + showIndication(INDICATION_TYPE_NONE); + } } else { - showIndication(INDICATION_TYPE_NONE); + scheduleShowNextIndication(minShowDuration - timeSinceLastIndicationSwitch); } } } @@ -164,11 +201,10 @@ public class KeyguardIndicationRotateTextViewController extends * - will continue to be in the rotation of messages shown until hideTransient is called. */ public void showTransient(CharSequence newIndication) { - final long inAnimationDuration = 600L; // see KeyguardIndicationTextView.getYInDuration updateIndication(INDICATION_TYPE_TRANSIENT, new KeyguardIndication.Builder() .setMessage(newIndication) - .setMinVisibilityMillis(2000L + inAnimationDuration) + .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION) .setTextColor(mInitialTextColorState) .build(), /* showImmediately */true); @@ -188,6 +224,15 @@ public class KeyguardIndicationRotateTextViewController extends return mIndicationMessages.keySet().size() > 0; } + /** + * Clears all messages in the queue and sets the current message to an empty string. + */ + public void clearMessages() { + mCurrIndicationType = INDICATION_TYPE_NONE; + mIndicationQueue.clear(); + mView.clearMessages(); + } + /** * Immediately show the passed indication type and schedule the next indication to show. * Will re-add this indication to be re-shown after all other indications have been @@ -196,27 +241,52 @@ public class KeyguardIndicationRotateTextViewController extends private void showIndication(@IndicationType int type) { cancelScheduledIndication(); + final CharSequence previousMessage = mCurrMessage; + final @IndicationType int previousIndicationType = mCurrIndicationType; mCurrIndicationType = type; + mCurrMessage = mIndicationMessages.get(type) != null + ? mIndicationMessages.get(type).getMessage() + : null; + mIndicationQueue.removeIf(x -> x == type); if (mCurrIndicationType != INDICATION_TYPE_NONE) { mIndicationQueue.add(type); // re-add to show later } - mView.switchIndication(mIndicationMessages.get(type)); + mLastIndicationSwitch = SystemClock.uptimeMillis(); + if (!TextUtils.equals(previousMessage, mCurrMessage) + || previousIndicationType != mCurrIndicationType) { + mView.switchIndication(mIndicationMessages.get(type)); + } // only schedule next indication if there's more than just this indication in the queue if (mCurrIndicationType != INDICATION_TYPE_NONE && mIndicationQueue.size() > 1) { - scheduleShowNextIndication(); + scheduleShowNextIndication(Math.max( + getMinVisibilityMillis(mIndicationMessages.get(type)), + DEFAULT_INDICATION_SHOW_LENGTH)); } } + private long getMinVisibilityMillis(KeyguardIndication indication) { + if (indication == null) { + return 0; + } + + if (indication.getMinVisibilityMillis() == null) { + return 0; + } + + return indication.getMinVisibilityMillis(); + } + protected boolean isNextIndicationScheduled() { return mShowNextIndicationRunnable != null; } - private void scheduleShowNextIndication() { + + private void scheduleShowNextIndication(long msUntilShowNextMsg) { cancelScheduledIndication(); - mShowNextIndicationRunnable = new ShowNextIndication(DEFAULT_INDICATION_SHOW_LENGTH); + mShowNextIndicationRunnable = new ShowNextIndication(msUntilShowNextMsg); } private void cancelScheduledIndication() { @@ -292,7 +362,9 @@ public class KeyguardIndicationRotateTextViewController extends } } + // only used locally to stop showing any messages & stop the rotating messages static final int INDICATION_TYPE_NONE = -1; + public static final int INDICATION_TYPE_OWNER_INFO = 0; public static final int INDICATION_TYPE_DISCLOSURE = 1; public static final int INDICATION_TYPE_LOGOUT = 2; @@ -303,6 +375,7 @@ public class KeyguardIndicationRotateTextViewController extends public static final int INDICATION_TYPE_RESTING = 7; public static final int INDICATION_TYPE_USER_LOCKED = 8; public static final int INDICATION_TYPE_REVERSE_CHARGING = 10; + public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11; @IntDef({ INDICATION_TYPE_NONE, @@ -316,6 +389,7 @@ public class KeyguardIndicationRotateTextViewController extends INDICATION_TYPE_RESTING, INDICATION_TYPE_USER_LOCKED, INDICATION_TYPE_REVERSE_CHARGING, + INDICATION_TYPE_BIOMETRIC_MESSAGE }) @Retention(RetentionPolicy.SOURCE) public @interface IndicationType{} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index f23a7cac7d2a3..f43d9c350d62d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -21,8 +21,10 @@ import static android.view.View.GONE; import static android.view.View.VISIBLE; import static com.android.systemui.DejankUtils.whitelistIpcs; +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_DISCLOSURE; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; @@ -94,6 +96,15 @@ 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 { @@ -103,6 +114,7 @@ public class KeyguardIndicationController { private static final int MSG_HIDE_TRANSIENT = 1; private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2; + private static final int MSG_HIDE_BIOMETRIC_MESSAGE = 3; private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300; private static final float BOUNCE_ANIMATION_FINAL_Y = 0f; @@ -132,9 +144,9 @@ public class KeyguardIndicationController { private String mRestingIndication; private String mAlignmentIndication; private CharSequence mTransientIndication; + private CharSequence mBiometricMessage; protected ColorStateList mInitialTextColorState; private boolean mVisible; - private boolean mHideTransientMessageOnScreenOff; private boolean mPowerPluggedIn; private boolean mPowerPluggedInWired; @@ -277,13 +289,15 @@ public class KeyguardIndicationController { } /** - * Doesn't include disclosure which gets triggered separately. + * Doesn't include disclosure (also a persistent indication) which gets triggered separately. + * + * This method also doesn't update transient messages like biometrics since those messages + * are also updated separately. */ - private void updateIndications(boolean animate, int userId) { + private void updatePersistentIndications(boolean animate, int userId) { updateOwnerInfo(); updateBattery(animate); updateUserLocked(userId); - updateTransient(); updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication()); updateAlignment(); updateLogoutView(); @@ -383,12 +397,36 @@ public class KeyguardIndicationController { } } + private void updateBiometricMessage() { + 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 (mDozing) { + updateIndication(false); + } + } + private void updateTransient() { if (!TextUtils.isEmpty(mTransientIndication)) { mRotateTextViewController.showTransient(mTransientIndication); } else { mRotateTextViewController.hideTransient(); } + + if (mDozing) { + updateIndication(false); + } } private void updateTrust(int userId, CharSequence trustGrantedIndication, @@ -576,6 +614,14 @@ public class KeyguardIndicationController { mHandler.obtainMessage(MSG_HIDE_TRANSIENT), delayMs); } + /** + * Hides biometric indication in {@param delayMs}. + */ + public void hideBiometricMessageDelayed(long delayMs) { + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_HIDE_BIOMETRIC_MESSAGE), delayMs); + } + /** * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. */ @@ -586,23 +632,40 @@ public class KeyguardIndicationController { /** * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. */ - public void showTransientIndication(CharSequence transientIndication) { - showTransientIndication(transientIndication, false /* isError */, - false /* hideOnScreenOff */); + private void showTransientIndication(CharSequence transientIndication) { + mTransientIndication = transientIndication; + mHandler.removeMessages(MSG_HIDE_TRANSIENT); + hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS); + + updateTransient(); } /** - * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. + * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}. */ - private void showTransientIndication(CharSequence transientIndication, - boolean isError, boolean hideOnScreenOff) { - mTransientIndication = transientIndication; - mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null; - mHandler.removeMessages(MSG_HIDE_TRANSIENT); - mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK); - hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS); + public void showBiometricMessage(int biometricMessage) { + showBiometricMessage(mContext.getResources().getString(biometricMessage)); + } - updateIndication(false); + /** + * Shows {@param biometricMessage} until it is hidden by {@link #hideBiometricMessage}. + */ + private void showBiometricMessage(CharSequence biometricMessage) { + mBiometricMessage = biometricMessage; + + mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK); + mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE); + hideBiometricMessageDelayed(BaseKeyguardCallback.HIDE_DELAY_MS); + + updateBiometricMessage(); + } + + private void hideBiometricMessage() { + if (mBiometricMessage != null) { + mBiometricMessage = null; + mHandler.removeMessages(MSG_HIDE_BIOMETRIC_MESSAGE); + updateBiometricMessage(); + } } /** @@ -611,10 +674,8 @@ public class KeyguardIndicationController { public void hideTransientIndication() { if (mTransientIndication != null) { mTransientIndication = null; - mHideTransientMessageOnScreenOff = false; mHandler.removeMessages(MSG_HIDE_TRANSIENT); - mRotateTextViewController.hideTransient(); - updateIndication(false); + updateTransient(); } } @@ -635,7 +696,11 @@ public class KeyguardIndicationController { // When dozing we ignore any text color and use white instead, because // colors can be hard to read in low brightness. mTopIndicationView.setTextColor(Color.WHITE); - if (!TextUtils.isEmpty(mTransientIndication)) { + if (!TextUtils.isEmpty(mBiometricMessage)) { + mWakeLock.setAcquired(true); + mTopIndicationView.switchIndication(mBiometricMessage, null, + true, () -> mWakeLock.setAcquired(false)); + } else if (!TextUtils.isEmpty(mTransientIndication)) { mWakeLock.setAcquired(true); mTopIndicationView.switchIndication(mTransientIndication, null, true, () -> mWakeLock.setAcquired(false)); @@ -669,7 +734,7 @@ public class KeyguardIndicationController { mTopIndicationView.setVisibility(GONE); mTopIndicationView.setText(null); mLockScreenIndicationView.setVisibility(View.VISIBLE); - updateIndications(animate, KeyguardUpdateMonitor.getCurrentUser()); + updatePersistentIndications(animate, KeyguardUpdateMonitor.getCurrentUser()); } // animates textView - textView moves up and bounces down @@ -798,6 +863,8 @@ public class KeyguardIndicationController { hideTransientIndication(); } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) { showActionToUnlock(); + } else if (msg.what == MSG_HIDE_BIOMETRIC_MESSAGE) { + hideBiometricMessage(); } } }; @@ -820,8 +887,7 @@ public class KeyguardIndicationController { mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState); } } else { - showTransientIndication(mContext.getString(R.string.keyguard_unlock), - false /* isError */, true /* hideOnScreenOff */); + showBiometricMessage(mContext.getString(R.string.keyguard_unlock)); } } @@ -830,15 +896,15 @@ public class KeyguardIndicationController { // if udfps available, there will always be a tappable affordance to unlock // For example, the lock icon if (mKeyguardBypassController.getUserHasDeviceEntryIntent()) { - showTransientIndication(R.string.keyguard_unlock_press); + showBiometricMessage(R.string.keyguard_unlock_press); } else if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) { // since face is locked out, simply show "try fingerprint" - showTransientIndication(R.string.keyguard_try_fingerprint); + showBiometricMessage(R.string.keyguard_try_fingerprint); } else { - showTransientIndication(R.string.keyguard_face_failed_use_fp); + showBiometricMessage(R.string.keyguard_face_failed_use_fp); } } else { - showTransientIndication(R.string.keyguard_try_fingerprint); + showBiometricMessage(R.string.keyguard_try_fingerprint); } // Although we suppress face auth errors visually, we still announce them for a11y @@ -857,6 +923,8 @@ public class KeyguardIndicationController { pw.println(" mChargingWattage: " + mChargingWattage); pw.println(" mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn); pw.println(" mDozing: " + mDozing); + pw.println(" mTransientIndication: " + mTransientIndication); + pw.println(" mBiometricMessage: " + mBiometricMessage); pw.println(" mBatteryLevel: " + mBatteryLevel); pw.println(" mBatteryPresent: " + mBatteryPresent); pw.println(" mTextView.getText(): " + ( @@ -871,7 +939,7 @@ public class KeyguardIndicationController { @Override public void onRefreshBatteryInfo(BatteryStatus status) { boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING - || status.status == BatteryManager.BATTERY_STATUS_FULL; + || status.isCharged(); boolean wasPluggedIn = mPowerPluggedIn; mPowerPluggedInWired = status.isPluggedInWired() && isChargingOrFull; mPowerPluggedInWireless = status.isPluggedInWireless() && isChargingOrFull; @@ -912,7 +980,6 @@ public class KeyguardIndicationController { .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) { return; } - boolean showActionToUnlock = msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; if (mStatusBarKeyguardViewManager.isBouncerShowing()) { @@ -921,14 +988,10 @@ public class KeyguardIndicationController { } else if (mKeyguardUpdateMonitor.isScreenOn()) { if (biometricSourceType == BiometricSourceType.FACE && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) { - // don't show any help messages, b/c they can come in right before a success - // However, continue to announce help messages for a11y - if (!TextUtils.isEmpty(helpString)) { - mLockScreenIndicationView.announceForAccessibility(helpString); - } + showTryFingerprintMsg(msgId, helpString); return; } - showTransientIndication(helpString, false /* isError */, showActionToUnlock); + showBiometricMessage(helpString); } else if (showActionToUnlock) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK), TRANSIENT_BIOMETRIC_ERROR_TIMEOUT); @@ -967,8 +1030,7 @@ public class KeyguardIndicationController { } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) { mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState); } else if (mKeyguardUpdateMonitor.isScreenOn()) { - showTransientIndication(errString, /* isError */ true, - /* hideOnScreenOff */ true); + showBiometricMessage(errString); } else { mMessageToShowOnScreenOn = errString; } @@ -1014,16 +1076,15 @@ public class KeyguardIndicationController { @Override public void onTrustAgentErrorMessage(CharSequence message) { - showTransientIndication(message, true /* isError */, false /* hideOnScreenOff */); + showBiometricMessage(message); } @Override public void onScreenTurnedOn() { if (mMessageToShowOnScreenOn != null) { - showTransientIndication(mMessageToShowOnScreenOn, true /* isError */, - false /* hideOnScreenOff */); + showBiometricMessage(mMessageToShowOnScreenOn); // We want to keep this message around in case the screen was off - hideTransientIndicationDelayed(HIDE_DELAY_MS); + hideBiometricMessageDelayed(HIDE_DELAY_MS); mMessageToShowOnScreenOn = null; } } @@ -1034,7 +1095,7 @@ public class KeyguardIndicationController { if (running && biometricSourceType == BiometricSourceType.FACE) { // Let's hide any previous messages when authentication starts, otherwise // multiple auth attempts would overlap. - hideTransientIndication(); + hideBiometricMessage(); mMessageToShowOnScreenOn = null; } } @@ -1043,11 +1104,11 @@ public class KeyguardIndicationController { public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType, boolean isStrongBiometric) { super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric); - mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT); + hideBiometricMessage(); if (biometricSourceType == BiometricSourceType.FACE && !mKeyguardBypassController.canBypass()) { - mHandler.sendEmptyMessage(MSG_SHOW_ACTION_TO_UNLOCK); + showActionToUnlock(); } } @@ -1074,8 +1135,7 @@ public class KeyguardIndicationController { @Override public void onRequireUnlockForNfc() { - showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc), - false /* isError */, false /* hideOnScreenOff */); + showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc)); hideTransientIndicationDelayed(HIDE_DELAY_MS); } } @@ -1094,8 +1154,8 @@ public class KeyguardIndicationController { } mDozing = dozing; - if (mHideTransientMessageOnScreenOff && mDozing) { - hideTransientIndication(); + if (mDozing) { + hideBiometricMessage(); } updateIndication(false); } @@ -1112,7 +1172,7 @@ public class KeyguardIndicationController { public void onKeyguardShowingChanged() { if (!mKeyguardStateController.isShowing()) { mTopIndicationView.clearMessages(); - mLockScreenIndicationView.clearMessages(); + mRotateTextViewController.clearMessages(); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java index 3a68b9c3d1b3d..339f371c0d12f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -35,23 +35,20 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.keyguard.KeyguardIndication; -import java.util.LinkedList; - /** * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open"). */ public class KeyguardIndicationTextView extends TextView { - private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500; - @StyleRes private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; @StyleRes private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; - private long mNextAnimationTime = 0; private boolean mAnimationsEnabled = true; - private LinkedList mMessages = new LinkedList<>(); - private LinkedList mKeyguardIndicationInfo = new LinkedList<>(); + private CharSequence mMessage; + private KeyguardIndication mKeyguardIndicationInfo; + + private Animator mLastAnimator; public KeyguardIndicationTextView(Context context) { super(context); @@ -71,22 +68,24 @@ public class KeyguardIndicationTextView extends TextView { } /** - * Clears message queue. + * Clears message queue and currently shown message. */ public void clearMessages() { - mMessages.clear(); - mKeyguardIndicationInfo.clear(); + if (mLastAnimator != null) { + mLastAnimator.cancel(); + } + setText(""); } /** - * Changes the text with an animation and makes sure a single indication is shown long enough. + * Changes the text with an animation. */ public void switchIndication(int textResId) { switchIndication(getResources().getText(textResId), null); } /** - * Changes the text with an animation and makes sure a single indication is shown long enough. + * Changes the text with an animation. * * @param indication The text to show. */ @@ -95,15 +94,14 @@ public class KeyguardIndicationTextView extends TextView { } /** - * Changes the text with an animation. Makes sure a single indication is shown long enough. + * Changes the text with an animation. */ public void switchIndication(CharSequence text, KeyguardIndication indication) { switchIndication(text, indication, true, null); } /** - * Changes the text with an optional animation. For animating text, makes sure a single - * indication is shown long enough. + * Updates the text with an optional animation. * * @param text The text to show. * @param indication optional display information for the text @@ -112,33 +110,15 @@ public class KeyguardIndicationTextView extends TextView { */ public void switchIndication(CharSequence text, KeyguardIndication indication, boolean animate, Runnable onAnimationEndCallback) { - if (text == null) text = ""; - - CharSequence lastPendingMessage = mMessages.peekLast(); - if (TextUtils.equals(lastPendingMessage, text) - || (lastPendingMessage == null && TextUtils.equals(text, getText()))) { - if (onAnimationEndCallback != null) { - onAnimationEndCallback.run(); - } - return; - } - mMessages.add(text); - mKeyguardIndicationInfo.add(indication); + mMessage = text; + mKeyguardIndicationInfo = indication; if (animate) { final boolean hasIcon = indication != null && indication.getIcon() != null; - final AnimatorSet animator = new AnimatorSet(); + AnimatorSet animator = new AnimatorSet(); // Make sure each animation is visible for a minimum amount of time, while not worrying // about fading in blank text - long timeInMillis = System.currentTimeMillis(); - long delay = Math.max(0, mNextAnimationTime - timeInMillis); - setNextAnimationTime(timeInMillis + delay + getFadeOutDuration()); - final long minDurationMillis = - (indication != null && indication.getMinVisibilityMillis() != null) - ? indication.getMinVisibilityMillis() - : MSG_MIN_DURATION_MILLIS_DEFAULT; - if (!text.equals("") || hasIcon) { - setNextAnimationTime(mNextAnimationTime + minDurationMillis); + if (!TextUtils.isEmpty(mMessage) || hasIcon) { Animator inAnimator = getInAnimator(); inAnimator.addListener(new AnimatorListenerAdapter() { @Override @@ -164,7 +144,10 @@ public class KeyguardIndicationTextView extends TextView { animator.play(outAnimator); } - animator.setStartDelay(delay); + if (mLastAnimator != null) { + mLastAnimator.cancel(); + } + mLastAnimator = animator; animator.start(); } else { setAlpha(1f); @@ -173,6 +156,10 @@ public class KeyguardIndicationTextView extends TextView { if (onAnimationEndCallback != null) { onAnimationEndCallback.run(); } + if (mLastAnimator != null) { + mLastAnimator.cancel(); + mLastAnimator = null; + } } } @@ -182,10 +169,20 @@ public class KeyguardIndicationTextView extends TextView { fadeOut.setDuration(getFadeOutDuration()); fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); fadeOut.addListener(new AnimatorListenerAdapter() { + private boolean mCancelled = false; @Override public void onAnimationEnd(Animator animator) { super.onAnimationEnd(animator); - setNextIndication(); + if (!mCancelled) { + setNextIndication(); + } + } + + @Override + public void onAnimationCancel(Animator animator) { + super.onAnimationCancel(animator); + mCancelled = true; + setAlpha(0); } }); @@ -198,20 +195,19 @@ public class KeyguardIndicationTextView extends TextView { } private void setNextIndication() { - KeyguardIndication info = mKeyguardIndicationInfo.poll(); - if (info != null) { + if (mKeyguardIndicationInfo != null) { // First, update the style. // If a background is set on the text, we don't want shadow on the text - if (info.getBackground() != null) { + if (mKeyguardIndicationInfo.getBackground() != null) { setTextAppearance(sButtonStyleId); } else { setTextAppearance(sStyleId); } - setBackground(info.getBackground()); - setTextColor(info.getTextColor()); - setOnClickListener(info.getClickListener()); - setClickable(info.getClickListener() != null); - final Drawable icon = info.getIcon(); + setBackground(mKeyguardIndicationInfo.getBackground()); + setTextColor(mKeyguardIndicationInfo.getTextColor()); + setOnClickListener(mKeyguardIndicationInfo.getClickListener()); + setClickable(mKeyguardIndicationInfo.getClickListener() != null); + final Drawable icon = mKeyguardIndicationInfo.getIcon(); if (icon != null) { icon.setTint(getCurrentTextColor()); if (icon instanceof AnimatedVectorDrawable) { @@ -220,7 +216,7 @@ public class KeyguardIndicationTextView extends TextView { } setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); } - setText(mMessages.poll()); + setText(mMessage); } private AnimatorSet getInAnimator() { @@ -238,6 +234,7 @@ public class KeyguardIndicationTextView extends TextView { public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); setTranslationY(0); + setAlpha(1f); } }); animatorSet.playTogether(yTranslate, fadeIn); @@ -270,14 +267,6 @@ public class KeyguardIndicationTextView extends TextView { return 167L; } - private void setNextAnimationTime(long time) { - if (mAnimationsEnabled) { - mNextAnimationTime = time; - } else { - mNextAnimationTime = 0L; - } - } - private int getYTranslationPixels() { return mContext.getResources().getDimensionPixelSize( com.android.systemui.R.dimen.keyguard_indication_y_translation); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java index 61b4041075199..22906761ac32f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java @@ -18,6 +18,7 @@ package com.android.systemui.keyguard; 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_DISCLOSURE; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; @@ -56,7 +57,8 @@ import org.mockito.MockitoAnnotations; public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCase { private static final String TEST_MESSAGE = "test message"; - private static final String TEST_MESSAGE_2 = "test message 2"; + private static final String TEST_MESSAGE_2 = "test message two"; + private int mMsgId = 0; @Mock private DelayableExecutor mExecutor; @@ -200,6 +202,24 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas verify(mExecutor).executeDelayed(any(), anyLong()); } + @Test + public void testSameMessage_noIndicationUpdate() { + // GIVEN we are showing and indication with a test message + mController.updateIndication( + INDICATION_TYPE_OWNER_INFO, createIndication(TEST_MESSAGE), true); + reset(mView); + reset(mExecutor); + + // WHEN the same type tries to show the same exact message + final KeyguardIndication sameIndication = createIndication(TEST_MESSAGE); + mController.updateIndication( + INDICATION_TYPE_OWNER_INFO, sameIndication, true); + + // THEN + // - we don't update the indication b/c there's no reason the animate the same text + verify(mView, never()).switchIndication(sameIndication); + } + @Test public void testTransientIndication() { // GIVEN we already have two indication messages @@ -223,8 +243,11 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas @Test public void testHideIndicationOneMessage() { // GIVEN we have one indication message + KeyguardIndication indication = createIndication(); mController.updateIndication( - INDICATION_TYPE_OWNER_INFO, createIndication(), false); + INDICATION_TYPE_OWNER_INFO, indication, false); + verify(mView).switchIndication(indication); + reset(mView); // WHEN we hide the current indication type mController.hideIndication(INDICATION_TYPE_OWNER_INFO); @@ -254,6 +277,10 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas @Test public void testStartDozing() { + // GIVEN a biometric message is showing + mController.updateIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE, + createIndication(), true); + // WHEN the device is dozing mStatusBarStateListener.onDozingChanged(true); @@ -293,9 +320,19 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas verify(mView, never()).switchIndication(any()); } + /** + * Create an indication with a unique message. + */ private KeyguardIndication createIndication() { + return createIndication(TEST_MESSAGE + " " + mMsgId++); + } + + /** + * Create an indication with the given message. + */ + private KeyguardIndication createIndication(String msg) { return new KeyguardIndication.Builder() - .setMessage(TEST_MESSAGE) + .setMessage(msg) .setTextColor(ColorStateList.valueOf(Color.WHITE)) .build(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 8afefde866324..e427d53306eae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -22,6 +22,7 @@ import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; 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_DISCLOSURE; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING; @@ -112,6 +113,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo", "bar"); + private static final int TEST_STRING_RES = R.string.keyguard_indication_trust_unlocked; + private String mKeyguardTryFingerprintMsg; private String mDisclosureWithOrganization; private String mDisclosureGeneric; @@ -419,7 +422,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN transient text is shown mStatusBarStateListener.onDozingChanged(true); - mController.showTransientIndication("Test"); + mController.showTransientIndication(TEST_STRING_RES); // THEN wake lock is held while the animation is running assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld()); @@ -434,7 +437,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN we show the transient indication mStatusBarStateListener.onDozingChanged(true); - mController.showTransientIndication("Test"); + mController.showTransientIndication(TEST_STRING_RES); // THEN wake lock is RELEASED, not held assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld()); @@ -445,10 +448,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { createController(); mController.setVisible(true); - mController.showTransientIndication("Test"); + mController.showTransientIndication(TEST_STRING_RES); mStatusBarStateListener.onDozingChanged(true); - assertThat(mTextView.getText()).isEqualTo("Test"); + assertThat(mTextView.getText()).isEqualTo( + mContext.getResources().getString(TEST_STRING_RES)); assertThat(mTextView.getCurrentTextColor()).isEqualTo(Color.WHITE); assertThat(mTextView.getAlpha()).isEqualTo(1f); } @@ -462,11 +466,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.getKeyguardCallback().onBiometricHelp( KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); - verifyTransientMessage(message); + verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); reset(mRotateTextViewController); mStatusBarStateListener.onDozingChanged(true); - verifyHideIndication(INDICATION_TYPE_TRANSIENT); + verifyHideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE); } @Test @@ -478,7 +482,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT, "A message", BiometricSourceType.FACE); - verifyTransientMessage(message); + verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); mStatusBarStateListener.onDozingChanged(true); assertThat(mTextView.getText()).isNotEqualTo(message); @@ -497,7 +501,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar", BiometricSourceType.FINGERPRINT); - verifyNoTransientMessage(); + verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); + verifyNoMessage(INDICATION_TYPE_TRANSIENT); } @Test @@ -757,7 +762,12 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verify(mRotateTextViewController).showTransient(eq(message)); } - private void verifyNoTransientMessage() { - verify(mRotateTextViewController, never()).showTransient(any()); + private void verifyNoMessage(int type) { + if (type == INDICATION_TYPE_TRANSIENT) { + verify(mRotateTextViewController, never()).showTransient(anyString()); + } else { + verify(mRotateTextViewController, never()).updateIndication(eq(type), + anyObject(), anyBoolean()); + } } }