Merge "KeyguardRotateController handles minimum show time" into sc-v2-dev

This commit is contained in:
Beverly Tai
2021-12-01 20:29:03 +00:00
committed by Android (Google) Code Review
5 changed files with 310 additions and 140 deletions

View File

@@ -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<KeyguardIndicationTextView> 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<Integer> 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{}

View File

@@ -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();
}
}
};

View File

@@ -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<CharSequence> mMessages = new LinkedList<>();
private LinkedList<KeyguardIndication> 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);

View File

@@ -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();
}

View File

@@ -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());
}
}
}