Loading packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +96 −22 Original line number Original line Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.keyguard; import android.annotation.Nullable; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.Color; import android.os.SystemClock; import android.text.TextUtils; import android.text.TextUtils; import androidx.annotation.IntDef; import androidx.annotation.IntDef; Loading @@ -40,13 +41,24 @@ import java.util.List; import java.util.Map; import java.util.Map; /** /** * Rotates through messages to show on the keyguard bottom area on the lock screen * Animates 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. * 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 public class KeyguardIndicationRotateTextViewController extends ViewController<KeyguardIndicationTextView> implements Dumpable { ViewController<KeyguardIndicationTextView> implements Dumpable { public static String TAG = "KgIndicationRotatingCtrl"; public static String TAG = "KgIndicationRotatingCtrl"; private static final long DEFAULT_INDICATION_SHOW_LENGTH = 3500; // milliseconds 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 StatusBarStateController mStatusBarStateController; private final float mMaxAlpha; private final float mMaxAlpha; Loading @@ -62,6 +74,8 @@ public class KeyguardIndicationRotateTextViewController extends // List of indication types to show. The next indication to show is always at index 0 // List of indication types to show. The next indication to show is always at index 0 private final List<Integer> mIndicationQueue = new LinkedList<>(); private final List<Integer> mIndicationQueue = new LinkedList<>(); private @IndicationType int mCurrIndicationType = INDICATION_TYPE_NONE; private @IndicationType int mCurrIndicationType = INDICATION_TYPE_NONE; private CharSequence mCurrMessage; private long mLastIndicationSwitch; private boolean mIsDozing; private boolean mIsDozing; Loading Loading @@ -94,17 +108,19 @@ public class KeyguardIndicationRotateTextViewController extends * Update the indication type with the given String. * Update the indication type with the given String. * @param type of indication * @param type of indication * @param newIndication message to associate with this indication type * @param newIndication message to associate with this indication type * @param showImmediately if true: shows this indication message immediately. Else, the text * @param showAsap if true: shows this indication message as soon as possible. If false, * associated with this type is updated and will show when its turn in * the text associated with this type is updated and will show when its turn * the IndicationQueue comes around. * in the IndicationQueue comes around. */ */ public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, boolean updateImmediately) { boolean showAsap) { if (type == INDICATION_TYPE_REVERSE_CHARGING) { if (type == INDICATION_TYPE_REVERSE_CHARGING) { // temporarily don't show here, instead use AmbientContainer b/181049781 // temporarily don't show here, instead use AmbientContainer b/181049781 return; 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; final boolean hasNewIndication = newIndication != null; if (!hasNewIndication) { if (!hasNewIndication) { mIndicationMessages.remove(type); mIndicationMessages.remove(type); Loading @@ -121,26 +137,47 @@ public class KeyguardIndicationRotateTextViewController extends return; return; } } final boolean showNow = updateImmediately long currTime = SystemClock.uptimeMillis(); || mCurrIndicationType == INDICATION_TYPE_NONE long timeSinceLastIndicationSwitch = currTime - mLastIndicationSwitch; || mCurrIndicationType == type; boolean currMsgShownForMinTime = timeSinceLastIndicationSwitch >= minShowDuration; if (hasNewIndication) { if (hasNewIndication) { if (showNow) { if (mCurrIndicationType == INDICATION_TYPE_NONE || mCurrIndicationType == type) { showIndication(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()) { } 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; return; } } // current indication is updated to empty if (mCurrIndicationType == type if (mCurrIndicationType == type && !hasNewIndication && !hasNewIndication && updateImmediately) { && showAsap) { if (currMsgShownForMinTime) { if (mShowNextIndicationRunnable != null) { if (mShowNextIndicationRunnable != null) { mShowNextIndicationRunnable.runImmediately(); mShowNextIndicationRunnable.runImmediately(); } else { } else { showIndication(INDICATION_TYPE_NONE); showIndication(INDICATION_TYPE_NONE); } } } else { scheduleShowNextIndication(minShowDuration - timeSinceLastIndicationSwitch); } } } } } Loading @@ -164,11 +201,10 @@ public class KeyguardIndicationRotateTextViewController extends * - will continue to be in the rotation of messages shown until hideTransient is called. * - will continue to be in the rotation of messages shown until hideTransient is called. */ */ public void showTransient(CharSequence newIndication) { public void showTransient(CharSequence newIndication) { final long inAnimationDuration = 600L; // see KeyguardIndicationTextView.getYInDuration updateIndication(INDICATION_TYPE_TRANSIENT, updateIndication(INDICATION_TYPE_TRANSIENT, new KeyguardIndication.Builder() new KeyguardIndication.Builder() .setMessage(newIndication) .setMessage(newIndication) .setMinVisibilityMillis(2000L + inAnimationDuration) .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION) .setTextColor(mInitialTextColorState) .setTextColor(mInitialTextColorState) .build(), .build(), /* showImmediately */true); /* showImmediately */true); Loading @@ -188,6 +224,15 @@ public class KeyguardIndicationRotateTextViewController extends return mIndicationMessages.keySet().size() > 0; 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. * 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 * Will re-add this indication to be re-shown after all other indications have been Loading @@ -196,27 +241,52 @@ public class KeyguardIndicationRotateTextViewController extends private void showIndication(@IndicationType int type) { private void showIndication(@IndicationType int type) { cancelScheduledIndication(); cancelScheduledIndication(); final CharSequence previousMessage = mCurrMessage; final @IndicationType int previousIndicationType = mCurrIndicationType; mCurrIndicationType = type; mCurrIndicationType = type; mCurrMessage = mIndicationMessages.get(type) != null ? mIndicationMessages.get(type).getMessage() : null; mIndicationQueue.removeIf(x -> x == type); mIndicationQueue.removeIf(x -> x == type); if (mCurrIndicationType != INDICATION_TYPE_NONE) { if (mCurrIndicationType != INDICATION_TYPE_NONE) { mIndicationQueue.add(type); // re-add to show later mIndicationQueue.add(type); // re-add to show later } } mLastIndicationSwitch = SystemClock.uptimeMillis(); if (!TextUtils.equals(previousMessage, mCurrMessage) || previousIndicationType != mCurrIndicationType) { mView.switchIndication(mIndicationMessages.get(type)); mView.switchIndication(mIndicationMessages.get(type)); } // only schedule next indication if there's more than just this indication in the queue // only schedule next indication if there's more than just this indication in the queue if (mCurrIndicationType != INDICATION_TYPE_NONE && mIndicationQueue.size() > 1) { 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() { protected boolean isNextIndicationScheduled() { return mShowNextIndicationRunnable != null; return mShowNextIndicationRunnable != null; } } private void scheduleShowNextIndication() { private void scheduleShowNextIndication(long msUntilShowNextMsg) { cancelScheduledIndication(); cancelScheduledIndication(); mShowNextIndicationRunnable = new ShowNextIndication(DEFAULT_INDICATION_SHOW_LENGTH); mShowNextIndicationRunnable = new ShowNextIndication(msUntilShowNextMsg); } } private void cancelScheduledIndication() { private void cancelScheduledIndication() { Loading Loading @@ -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; static final int INDICATION_TYPE_NONE = -1; public static final int INDICATION_TYPE_OWNER_INFO = 0; public static final int INDICATION_TYPE_OWNER_INFO = 0; public static final int INDICATION_TYPE_DISCLOSURE = 1; public static final int INDICATION_TYPE_DISCLOSURE = 1; public static final int INDICATION_TYPE_LOGOUT = 2; public static final int INDICATION_TYPE_LOGOUT = 2; Loading @@ -303,6 +375,7 @@ public class KeyguardIndicationRotateTextViewController extends public static final int INDICATION_TYPE_RESTING = 7; public static final int INDICATION_TYPE_RESTING = 7; public static final int INDICATION_TYPE_USER_LOCKED = 8; public static final int INDICATION_TYPE_USER_LOCKED = 8; public static final int INDICATION_TYPE_REVERSE_CHARGING = 10; public static final int INDICATION_TYPE_REVERSE_CHARGING = 10; public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11; @IntDef({ @IntDef({ INDICATION_TYPE_NONE, INDICATION_TYPE_NONE, Loading @@ -316,6 +389,7 @@ public class KeyguardIndicationRotateTextViewController extends INDICATION_TYPE_RESTING, INDICATION_TYPE_RESTING, INDICATION_TYPE_USER_LOCKED, INDICATION_TYPE_USER_LOCKED, INDICATION_TYPE_REVERSE_CHARGING, INDICATION_TYPE_REVERSE_CHARGING, INDICATION_TYPE_BIOMETRIC_MESSAGE }) }) @Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE) public @interface IndicationType{} public @interface IndicationType{} Loading packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +108 −48 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +45 −56 Original line number Original line Diff line number Diff line Loading @@ -35,23 +35,20 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.Interpolators; import com.android.systemui.keyguard.KeyguardIndication; 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"). * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open"). */ */ public class KeyguardIndicationTextView extends TextView { public class KeyguardIndicationTextView extends TextView { private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500; @StyleRes @StyleRes private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; @StyleRes @StyleRes private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; private long mNextAnimationTime = 0; private boolean mAnimationsEnabled = true; private boolean mAnimationsEnabled = true; private LinkedList<CharSequence> mMessages = new LinkedList<>(); private CharSequence mMessage; private LinkedList<KeyguardIndication> mKeyguardIndicationInfo = new LinkedList<>(); private KeyguardIndication mKeyguardIndicationInfo; private Animator mLastAnimator; public KeyguardIndicationTextView(Context context) { public KeyguardIndicationTextView(Context context) { super(context); super(context); Loading @@ -71,22 +68,24 @@ public class KeyguardIndicationTextView extends TextView { } } /** /** * Clears message queue. * Clears message queue and currently shown message. */ */ public void clearMessages() { public void clearMessages() { mMessages.clear(); if (mLastAnimator != null) { mKeyguardIndicationInfo.clear(); 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) { public void switchIndication(int textResId) { switchIndication(getResources().getText(textResId), null); 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. * @param indication The text to show. */ */ Loading @@ -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) { public void switchIndication(CharSequence text, KeyguardIndication indication) { switchIndication(text, indication, true, null); switchIndication(text, indication, true, null); } } /** /** * Changes the text with an optional animation. For animating text, makes sure a single * Updates the text with an optional animation. * indication is shown long enough. * * * @param text The text to show. * @param text The text to show. * @param indication optional display information for the text * @param indication optional display information for the text Loading @@ -112,33 +110,15 @@ public class KeyguardIndicationTextView extends TextView { */ */ public void switchIndication(CharSequence text, KeyguardIndication indication, public void switchIndication(CharSequence text, KeyguardIndication indication, boolean animate, Runnable onAnimationEndCallback) { boolean animate, Runnable onAnimationEndCallback) { if (text == null) text = ""; mMessage = text; mKeyguardIndicationInfo = indication; 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); if (animate) { if (animate) { final boolean hasIcon = indication != null && indication.getIcon() != null; 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 // Make sure each animation is visible for a minimum amount of time, while not worrying // about fading in blank text // about fading in blank text long timeInMillis = System.currentTimeMillis(); if (!TextUtils.isEmpty(mMessage) || hasIcon) { 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); Animator inAnimator = getInAnimator(); Animator inAnimator = getInAnimator(); inAnimator.addListener(new AnimatorListenerAdapter() { inAnimator.addListener(new AnimatorListenerAdapter() { @Override @Override Loading @@ -164,7 +144,10 @@ public class KeyguardIndicationTextView extends TextView { animator.play(outAnimator); animator.play(outAnimator); } } animator.setStartDelay(delay); if (mLastAnimator != null) { mLastAnimator.cancel(); } mLastAnimator = animator; animator.start(); animator.start(); } else { } else { setAlpha(1f); setAlpha(1f); Loading @@ -173,6 +156,10 @@ public class KeyguardIndicationTextView extends TextView { if (onAnimationEndCallback != null) { if (onAnimationEndCallback != null) { onAnimationEndCallback.run(); onAnimationEndCallback.run(); } } if (mLastAnimator != null) { mLastAnimator.cancel(); mLastAnimator = null; } } } } } Loading @@ -182,11 +169,21 @@ public class KeyguardIndicationTextView extends TextView { fadeOut.setDuration(getFadeOutDuration()); fadeOut.setDuration(getFadeOutDuration()); fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); fadeOut.addListener(new AnimatorListenerAdapter() { fadeOut.addListener(new AnimatorListenerAdapter() { private boolean mCancelled = false; @Override @Override public void onAnimationEnd(Animator animator) { public void onAnimationEnd(Animator animator) { super.onAnimationEnd(animator); super.onAnimationEnd(animator); if (!mCancelled) { setNextIndication(); setNextIndication(); } } } @Override public void onAnimationCancel(Animator animator) { super.onAnimationCancel(animator); mCancelled = true; setAlpha(0); } }); }); Animator yTranslate = Animator yTranslate = Loading @@ -198,20 +195,19 @@ public class KeyguardIndicationTextView extends TextView { } } private void setNextIndication() { private void setNextIndication() { KeyguardIndication info = mKeyguardIndicationInfo.poll(); if (mKeyguardIndicationInfo != null) { if (info != null) { // First, update the style. // First, update the style. // If a background is set on the text, we don't want shadow on the text // 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); setTextAppearance(sButtonStyleId); } else { } else { setTextAppearance(sStyleId); setTextAppearance(sStyleId); } } setBackground(info.getBackground()); setBackground(mKeyguardIndicationInfo.getBackground()); setTextColor(info.getTextColor()); setTextColor(mKeyguardIndicationInfo.getTextColor()); setOnClickListener(info.getClickListener()); setOnClickListener(mKeyguardIndicationInfo.getClickListener()); setClickable(info.getClickListener() != null); setClickable(mKeyguardIndicationInfo.getClickListener() != null); final Drawable icon = info.getIcon(); final Drawable icon = mKeyguardIndicationInfo.getIcon(); if (icon != null) { if (icon != null) { icon.setTint(getCurrentTextColor()); icon.setTint(getCurrentTextColor()); if (icon instanceof AnimatedVectorDrawable) { if (icon instanceof AnimatedVectorDrawable) { Loading @@ -220,7 +216,7 @@ public class KeyguardIndicationTextView extends TextView { } } setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); } } setText(mMessages.poll()); setText(mMessage); } } private AnimatorSet getInAnimator() { private AnimatorSet getInAnimator() { Loading @@ -238,6 +234,7 @@ public class KeyguardIndicationTextView extends TextView { public void onAnimationCancel(Animator animation) { public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); super.onAnimationCancel(animation); setTranslationY(0); setTranslationY(0); setAlpha(1f); } } }); }); animatorSet.playTogether(yTranslate, fadeIn); animatorSet.playTogether(yTranslate, fadeIn); Loading Loading @@ -270,14 +267,6 @@ public class KeyguardIndicationTextView extends TextView { return 167L; return 167L; } } private void setNextAnimationTime(long time) { if (mAnimationsEnabled) { mNextAnimationTime = time; } else { mNextAnimationTime = 0L; } } private int getYTranslationPixels() { private int getYTranslationPixels() { return mContext.getResources().getDimensionPixelSize( return mContext.getResources().getDimensionPixelSize( com.android.systemui.R.dimen.keyguard_indication_y_translation); com.android.systemui.R.dimen.keyguard_indication_y_translation); Loading packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java +40 −3 Original line number Original line Diff line number Diff line Loading @@ -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_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_DISCLOSURE; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; Loading Loading @@ -56,7 +57,8 @@ import org.mockito.MockitoAnnotations; public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCase { public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCase { private static final String TEST_MESSAGE = "test message"; 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 @Mock private DelayableExecutor mExecutor; private DelayableExecutor mExecutor; Loading Loading @@ -200,6 +202,24 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas verify(mExecutor).executeDelayed(any(), anyLong()); 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 @Test public void testTransientIndication() { public void testTransientIndication() { // GIVEN we already have two indication messages // GIVEN we already have two indication messages Loading @@ -223,8 +243,11 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas @Test @Test public void testHideIndicationOneMessage() { public void testHideIndicationOneMessage() { // GIVEN we have one indication message // GIVEN we have one indication message KeyguardIndication indication = createIndication(); mController.updateIndication( 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 // WHEN we hide the current indication type mController.hideIndication(INDICATION_TYPE_OWNER_INFO); mController.hideIndication(INDICATION_TYPE_OWNER_INFO); Loading Loading @@ -254,6 +277,10 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas @Test @Test public void testStartDozing() { public void testStartDozing() { // GIVEN a biometric message is showing mController.updateIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE, createIndication(), true); // WHEN the device is dozing // WHEN the device is dozing mStatusBarStateListener.onDozingChanged(true); mStatusBarStateListener.onDozingChanged(true); Loading Loading @@ -293,9 +320,19 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas verify(mView, never()).switchIndication(any()); verify(mView, never()).switchIndication(any()); } } /** * Create an indication with a unique message. */ private KeyguardIndication createIndication() { private KeyguardIndication createIndication() { return createIndication(TEST_MESSAGE + " " + mMsgId++); } /** * Create an indication with the given message. */ private KeyguardIndication createIndication(String msg) { return new KeyguardIndication.Builder() return new KeyguardIndication.Builder() .setMessage(TEST_MESSAGE) .setMessage(msg) .setTextColor(ColorStateList.valueOf(Color.WHITE)) .setTextColor(ColorStateList.valueOf(Color.WHITE)) .build(); .build(); } } Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +20 −10 Original line number Original line Diff line number Diff line Loading @@ -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_ALIGNMENT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; 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_DISCLOSURE; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING; Loading Loading @@ -112,6 +113,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo", private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo", "bar"); "bar"); private static final int TEST_STRING_RES = R.string.keyguard_indication_trust_unlocked; private String mKeyguardTryFingerprintMsg; private String mKeyguardTryFingerprintMsg; private String mDisclosureWithOrganization; private String mDisclosureWithOrganization; private String mDisclosureGeneric; private String mDisclosureGeneric; Loading Loading @@ -419,7 +422,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN transient text is shown // WHEN transient text is shown mStatusBarStateListener.onDozingChanged(true); mStatusBarStateListener.onDozingChanged(true); mController.showTransientIndication("Test"); mController.showTransientIndication(TEST_STRING_RES); // THEN wake lock is held while the animation is running // THEN wake lock is held while the animation is running assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld()); assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld()); Loading @@ -434,7 +437,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN we show the transient indication // WHEN we show the transient indication mStatusBarStateListener.onDozingChanged(true); mStatusBarStateListener.onDozingChanged(true); mController.showTransientIndication("Test"); mController.showTransientIndication(TEST_STRING_RES); // THEN wake lock is RELEASED, not held // THEN wake lock is RELEASED, not held assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld()); assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld()); Loading @@ -445,10 +448,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { createController(); createController(); mController.setVisible(true); mController.setVisible(true); mController.showTransientIndication("Test"); mController.showTransientIndication(TEST_STRING_RES); mStatusBarStateListener.onDozingChanged(true); 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.getCurrentTextColor()).isEqualTo(Color.WHITE); assertThat(mTextView.getAlpha()).isEqualTo(1f); assertThat(mTextView.getAlpha()).isEqualTo(1f); } } Loading @@ -462,11 +466,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.getKeyguardCallback().onBiometricHelp( mController.getKeyguardCallback().onBiometricHelp( KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); BiometricSourceType.FACE); verifyTransientMessage(message); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); reset(mRotateTextViewController); reset(mRotateTextViewController); mStatusBarStateListener.onDozingChanged(true); mStatusBarStateListener.onDozingChanged(true); verifyHideIndication(INDICATION_TYPE_TRANSIENT); verifyHideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE); } } @Test @Test Loading @@ -478,7 +482,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT, mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT, "A message", BiometricSourceType.FACE); "A message", BiometricSourceType.FACE); verifyTransientMessage(message); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); mStatusBarStateListener.onDozingChanged(true); mStatusBarStateListener.onDozingChanged(true); assertThat(mTextView.getText()).isNotEqualTo(message); assertThat(mTextView.getText()).isNotEqualTo(message); Loading @@ -497,7 +501,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar", FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar", BiometricSourceType.FINGERPRINT); BiometricSourceType.FINGERPRINT); verifyNoTransientMessage(); verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); verifyNoMessage(INDICATION_TYPE_TRANSIENT); } } @Test @Test Loading Loading @@ -757,7 +762,12 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verify(mRotateTextViewController).showTransient(eq(message)); verify(mRotateTextViewController).showTransient(eq(message)); } } private void verifyNoTransientMessage() { private void verifyNoMessage(int type) { verify(mRotateTextViewController, never()).showTransient(any()); if (type == INDICATION_TYPE_TRANSIENT) { verify(mRotateTextViewController, never()).showTransient(anyString()); } else { verify(mRotateTextViewController, never()).updateIndication(eq(type), anyObject(), anyBoolean()); } } } } } Loading
packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +96 −22 Original line number Original line Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.keyguard; import android.annotation.Nullable; import android.annotation.Nullable; import android.content.res.ColorStateList; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.Color; import android.os.SystemClock; import android.text.TextUtils; import android.text.TextUtils; import androidx.annotation.IntDef; import androidx.annotation.IntDef; Loading @@ -40,13 +41,24 @@ import java.util.List; import java.util.Map; import java.util.Map; /** /** * Rotates through messages to show on the keyguard bottom area on the lock screen * Animates 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. * 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 public class KeyguardIndicationRotateTextViewController extends ViewController<KeyguardIndicationTextView> implements Dumpable { ViewController<KeyguardIndicationTextView> implements Dumpable { public static String TAG = "KgIndicationRotatingCtrl"; public static String TAG = "KgIndicationRotatingCtrl"; private static final long DEFAULT_INDICATION_SHOW_LENGTH = 3500; // milliseconds 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 StatusBarStateController mStatusBarStateController; private final float mMaxAlpha; private final float mMaxAlpha; Loading @@ -62,6 +74,8 @@ public class KeyguardIndicationRotateTextViewController extends // List of indication types to show. The next indication to show is always at index 0 // List of indication types to show. The next indication to show is always at index 0 private final List<Integer> mIndicationQueue = new LinkedList<>(); private final List<Integer> mIndicationQueue = new LinkedList<>(); private @IndicationType int mCurrIndicationType = INDICATION_TYPE_NONE; private @IndicationType int mCurrIndicationType = INDICATION_TYPE_NONE; private CharSequence mCurrMessage; private long mLastIndicationSwitch; private boolean mIsDozing; private boolean mIsDozing; Loading Loading @@ -94,17 +108,19 @@ public class KeyguardIndicationRotateTextViewController extends * Update the indication type with the given String. * Update the indication type with the given String. * @param type of indication * @param type of indication * @param newIndication message to associate with this indication type * @param newIndication message to associate with this indication type * @param showImmediately if true: shows this indication message immediately. Else, the text * @param showAsap if true: shows this indication message as soon as possible. If false, * associated with this type is updated and will show when its turn in * the text associated with this type is updated and will show when its turn * the IndicationQueue comes around. * in the IndicationQueue comes around. */ */ public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, boolean updateImmediately) { boolean showAsap) { if (type == INDICATION_TYPE_REVERSE_CHARGING) { if (type == INDICATION_TYPE_REVERSE_CHARGING) { // temporarily don't show here, instead use AmbientContainer b/181049781 // temporarily don't show here, instead use AmbientContainer b/181049781 return; 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; final boolean hasNewIndication = newIndication != null; if (!hasNewIndication) { if (!hasNewIndication) { mIndicationMessages.remove(type); mIndicationMessages.remove(type); Loading @@ -121,26 +137,47 @@ public class KeyguardIndicationRotateTextViewController extends return; return; } } final boolean showNow = updateImmediately long currTime = SystemClock.uptimeMillis(); || mCurrIndicationType == INDICATION_TYPE_NONE long timeSinceLastIndicationSwitch = currTime - mLastIndicationSwitch; || mCurrIndicationType == type; boolean currMsgShownForMinTime = timeSinceLastIndicationSwitch >= minShowDuration; if (hasNewIndication) { if (hasNewIndication) { if (showNow) { if (mCurrIndicationType == INDICATION_TYPE_NONE || mCurrIndicationType == type) { showIndication(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()) { } 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; return; } } // current indication is updated to empty if (mCurrIndicationType == type if (mCurrIndicationType == type && !hasNewIndication && !hasNewIndication && updateImmediately) { && showAsap) { if (currMsgShownForMinTime) { if (mShowNextIndicationRunnable != null) { if (mShowNextIndicationRunnable != null) { mShowNextIndicationRunnable.runImmediately(); mShowNextIndicationRunnable.runImmediately(); } else { } else { showIndication(INDICATION_TYPE_NONE); showIndication(INDICATION_TYPE_NONE); } } } else { scheduleShowNextIndication(minShowDuration - timeSinceLastIndicationSwitch); } } } } } Loading @@ -164,11 +201,10 @@ public class KeyguardIndicationRotateTextViewController extends * - will continue to be in the rotation of messages shown until hideTransient is called. * - will continue to be in the rotation of messages shown until hideTransient is called. */ */ public void showTransient(CharSequence newIndication) { public void showTransient(CharSequence newIndication) { final long inAnimationDuration = 600L; // see KeyguardIndicationTextView.getYInDuration updateIndication(INDICATION_TYPE_TRANSIENT, updateIndication(INDICATION_TYPE_TRANSIENT, new KeyguardIndication.Builder() new KeyguardIndication.Builder() .setMessage(newIndication) .setMessage(newIndication) .setMinVisibilityMillis(2000L + inAnimationDuration) .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION) .setTextColor(mInitialTextColorState) .setTextColor(mInitialTextColorState) .build(), .build(), /* showImmediately */true); /* showImmediately */true); Loading @@ -188,6 +224,15 @@ public class KeyguardIndicationRotateTextViewController extends return mIndicationMessages.keySet().size() > 0; 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. * 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 * Will re-add this indication to be re-shown after all other indications have been Loading @@ -196,27 +241,52 @@ public class KeyguardIndicationRotateTextViewController extends private void showIndication(@IndicationType int type) { private void showIndication(@IndicationType int type) { cancelScheduledIndication(); cancelScheduledIndication(); final CharSequence previousMessage = mCurrMessage; final @IndicationType int previousIndicationType = mCurrIndicationType; mCurrIndicationType = type; mCurrIndicationType = type; mCurrMessage = mIndicationMessages.get(type) != null ? mIndicationMessages.get(type).getMessage() : null; mIndicationQueue.removeIf(x -> x == type); mIndicationQueue.removeIf(x -> x == type); if (mCurrIndicationType != INDICATION_TYPE_NONE) { if (mCurrIndicationType != INDICATION_TYPE_NONE) { mIndicationQueue.add(type); // re-add to show later mIndicationQueue.add(type); // re-add to show later } } mLastIndicationSwitch = SystemClock.uptimeMillis(); if (!TextUtils.equals(previousMessage, mCurrMessage) || previousIndicationType != mCurrIndicationType) { mView.switchIndication(mIndicationMessages.get(type)); mView.switchIndication(mIndicationMessages.get(type)); } // only schedule next indication if there's more than just this indication in the queue // only schedule next indication if there's more than just this indication in the queue if (mCurrIndicationType != INDICATION_TYPE_NONE && mIndicationQueue.size() > 1) { 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() { protected boolean isNextIndicationScheduled() { return mShowNextIndicationRunnable != null; return mShowNextIndicationRunnable != null; } } private void scheduleShowNextIndication() { private void scheduleShowNextIndication(long msUntilShowNextMsg) { cancelScheduledIndication(); cancelScheduledIndication(); mShowNextIndicationRunnable = new ShowNextIndication(DEFAULT_INDICATION_SHOW_LENGTH); mShowNextIndicationRunnable = new ShowNextIndication(msUntilShowNextMsg); } } private void cancelScheduledIndication() { private void cancelScheduledIndication() { Loading Loading @@ -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; static final int INDICATION_TYPE_NONE = -1; public static final int INDICATION_TYPE_OWNER_INFO = 0; public static final int INDICATION_TYPE_OWNER_INFO = 0; public static final int INDICATION_TYPE_DISCLOSURE = 1; public static final int INDICATION_TYPE_DISCLOSURE = 1; public static final int INDICATION_TYPE_LOGOUT = 2; public static final int INDICATION_TYPE_LOGOUT = 2; Loading @@ -303,6 +375,7 @@ public class KeyguardIndicationRotateTextViewController extends public static final int INDICATION_TYPE_RESTING = 7; public static final int INDICATION_TYPE_RESTING = 7; public static final int INDICATION_TYPE_USER_LOCKED = 8; public static final int INDICATION_TYPE_USER_LOCKED = 8; public static final int INDICATION_TYPE_REVERSE_CHARGING = 10; public static final int INDICATION_TYPE_REVERSE_CHARGING = 10; public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11; @IntDef({ @IntDef({ INDICATION_TYPE_NONE, INDICATION_TYPE_NONE, Loading @@ -316,6 +389,7 @@ public class KeyguardIndicationRotateTextViewController extends INDICATION_TYPE_RESTING, INDICATION_TYPE_RESTING, INDICATION_TYPE_USER_LOCKED, INDICATION_TYPE_USER_LOCKED, INDICATION_TYPE_REVERSE_CHARGING, INDICATION_TYPE_REVERSE_CHARGING, INDICATION_TYPE_BIOMETRIC_MESSAGE }) }) @Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE) public @interface IndicationType{} public @interface IndicationType{} Loading
packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +108 −48 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +45 −56 Original line number Original line Diff line number Diff line Loading @@ -35,23 +35,20 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.Interpolators; import com.android.systemui.keyguard.KeyguardIndication; 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"). * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open"). */ */ public class KeyguardIndicationTextView extends TextView { public class KeyguardIndicationTextView extends TextView { private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500; @StyleRes @StyleRes private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; @StyleRes @StyleRes private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; private long mNextAnimationTime = 0; private boolean mAnimationsEnabled = true; private boolean mAnimationsEnabled = true; private LinkedList<CharSequence> mMessages = new LinkedList<>(); private CharSequence mMessage; private LinkedList<KeyguardIndication> mKeyguardIndicationInfo = new LinkedList<>(); private KeyguardIndication mKeyguardIndicationInfo; private Animator mLastAnimator; public KeyguardIndicationTextView(Context context) { public KeyguardIndicationTextView(Context context) { super(context); super(context); Loading @@ -71,22 +68,24 @@ public class KeyguardIndicationTextView extends TextView { } } /** /** * Clears message queue. * Clears message queue and currently shown message. */ */ public void clearMessages() { public void clearMessages() { mMessages.clear(); if (mLastAnimator != null) { mKeyguardIndicationInfo.clear(); 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) { public void switchIndication(int textResId) { switchIndication(getResources().getText(textResId), null); 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. * @param indication The text to show. */ */ Loading @@ -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) { public void switchIndication(CharSequence text, KeyguardIndication indication) { switchIndication(text, indication, true, null); switchIndication(text, indication, true, null); } } /** /** * Changes the text with an optional animation. For animating text, makes sure a single * Updates the text with an optional animation. * indication is shown long enough. * * * @param text The text to show. * @param text The text to show. * @param indication optional display information for the text * @param indication optional display information for the text Loading @@ -112,33 +110,15 @@ public class KeyguardIndicationTextView extends TextView { */ */ public void switchIndication(CharSequence text, KeyguardIndication indication, public void switchIndication(CharSequence text, KeyguardIndication indication, boolean animate, Runnable onAnimationEndCallback) { boolean animate, Runnable onAnimationEndCallback) { if (text == null) text = ""; mMessage = text; mKeyguardIndicationInfo = indication; 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); if (animate) { if (animate) { final boolean hasIcon = indication != null && indication.getIcon() != null; 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 // Make sure each animation is visible for a minimum amount of time, while not worrying // about fading in blank text // about fading in blank text long timeInMillis = System.currentTimeMillis(); if (!TextUtils.isEmpty(mMessage) || hasIcon) { 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); Animator inAnimator = getInAnimator(); Animator inAnimator = getInAnimator(); inAnimator.addListener(new AnimatorListenerAdapter() { inAnimator.addListener(new AnimatorListenerAdapter() { @Override @Override Loading @@ -164,7 +144,10 @@ public class KeyguardIndicationTextView extends TextView { animator.play(outAnimator); animator.play(outAnimator); } } animator.setStartDelay(delay); if (mLastAnimator != null) { mLastAnimator.cancel(); } mLastAnimator = animator; animator.start(); animator.start(); } else { } else { setAlpha(1f); setAlpha(1f); Loading @@ -173,6 +156,10 @@ public class KeyguardIndicationTextView extends TextView { if (onAnimationEndCallback != null) { if (onAnimationEndCallback != null) { onAnimationEndCallback.run(); onAnimationEndCallback.run(); } } if (mLastAnimator != null) { mLastAnimator.cancel(); mLastAnimator = null; } } } } } Loading @@ -182,11 +169,21 @@ public class KeyguardIndicationTextView extends TextView { fadeOut.setDuration(getFadeOutDuration()); fadeOut.setDuration(getFadeOutDuration()); fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); fadeOut.addListener(new AnimatorListenerAdapter() { fadeOut.addListener(new AnimatorListenerAdapter() { private boolean mCancelled = false; @Override @Override public void onAnimationEnd(Animator animator) { public void onAnimationEnd(Animator animator) { super.onAnimationEnd(animator); super.onAnimationEnd(animator); if (!mCancelled) { setNextIndication(); setNextIndication(); } } } @Override public void onAnimationCancel(Animator animator) { super.onAnimationCancel(animator); mCancelled = true; setAlpha(0); } }); }); Animator yTranslate = Animator yTranslate = Loading @@ -198,20 +195,19 @@ public class KeyguardIndicationTextView extends TextView { } } private void setNextIndication() { private void setNextIndication() { KeyguardIndication info = mKeyguardIndicationInfo.poll(); if (mKeyguardIndicationInfo != null) { if (info != null) { // First, update the style. // First, update the style. // If a background is set on the text, we don't want shadow on the text // 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); setTextAppearance(sButtonStyleId); } else { } else { setTextAppearance(sStyleId); setTextAppearance(sStyleId); } } setBackground(info.getBackground()); setBackground(mKeyguardIndicationInfo.getBackground()); setTextColor(info.getTextColor()); setTextColor(mKeyguardIndicationInfo.getTextColor()); setOnClickListener(info.getClickListener()); setOnClickListener(mKeyguardIndicationInfo.getClickListener()); setClickable(info.getClickListener() != null); setClickable(mKeyguardIndicationInfo.getClickListener() != null); final Drawable icon = info.getIcon(); final Drawable icon = mKeyguardIndicationInfo.getIcon(); if (icon != null) { if (icon != null) { icon.setTint(getCurrentTextColor()); icon.setTint(getCurrentTextColor()); if (icon instanceof AnimatedVectorDrawable) { if (icon instanceof AnimatedVectorDrawable) { Loading @@ -220,7 +216,7 @@ public class KeyguardIndicationTextView extends TextView { } } setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); } } setText(mMessages.poll()); setText(mMessage); } } private AnimatorSet getInAnimator() { private AnimatorSet getInAnimator() { Loading @@ -238,6 +234,7 @@ public class KeyguardIndicationTextView extends TextView { public void onAnimationCancel(Animator animation) { public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); super.onAnimationCancel(animation); setTranslationY(0); setTranslationY(0); setAlpha(1f); } } }); }); animatorSet.playTogether(yTranslate, fadeIn); animatorSet.playTogether(yTranslate, fadeIn); Loading Loading @@ -270,14 +267,6 @@ public class KeyguardIndicationTextView extends TextView { return 167L; return 167L; } } private void setNextAnimationTime(long time) { if (mAnimationsEnabled) { mNextAnimationTime = time; } else { mNextAnimationTime = 0L; } } private int getYTranslationPixels() { private int getYTranslationPixels() { return mContext.getResources().getDimensionPixelSize( return mContext.getResources().getDimensionPixelSize( com.android.systemui.R.dimen.keyguard_indication_y_translation); com.android.systemui.R.dimen.keyguard_indication_y_translation); Loading
packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java +40 −3 Original line number Original line Diff line number Diff line Loading @@ -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_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_DISCLOSURE; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; Loading Loading @@ -56,7 +57,8 @@ import org.mockito.MockitoAnnotations; public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCase { public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCase { private static final String TEST_MESSAGE = "test message"; 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 @Mock private DelayableExecutor mExecutor; private DelayableExecutor mExecutor; Loading Loading @@ -200,6 +202,24 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas verify(mExecutor).executeDelayed(any(), anyLong()); 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 @Test public void testTransientIndication() { public void testTransientIndication() { // GIVEN we already have two indication messages // GIVEN we already have two indication messages Loading @@ -223,8 +243,11 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas @Test @Test public void testHideIndicationOneMessage() { public void testHideIndicationOneMessage() { // GIVEN we have one indication message // GIVEN we have one indication message KeyguardIndication indication = createIndication(); mController.updateIndication( 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 // WHEN we hide the current indication type mController.hideIndication(INDICATION_TYPE_OWNER_INFO); mController.hideIndication(INDICATION_TYPE_OWNER_INFO); Loading Loading @@ -254,6 +277,10 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas @Test @Test public void testStartDozing() { public void testStartDozing() { // GIVEN a biometric message is showing mController.updateIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE, createIndication(), true); // WHEN the device is dozing // WHEN the device is dozing mStatusBarStateListener.onDozingChanged(true); mStatusBarStateListener.onDozingChanged(true); Loading Loading @@ -293,9 +320,19 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas verify(mView, never()).switchIndication(any()); verify(mView, never()).switchIndication(any()); } } /** * Create an indication with a unique message. */ private KeyguardIndication createIndication() { private KeyguardIndication createIndication() { return createIndication(TEST_MESSAGE + " " + mMsgId++); } /** * Create an indication with the given message. */ private KeyguardIndication createIndication(String msg) { return new KeyguardIndication.Builder() return new KeyguardIndication.Builder() .setMessage(TEST_MESSAGE) .setMessage(msg) .setTextColor(ColorStateList.valueOf(Color.WHITE)) .setTextColor(ColorStateList.valueOf(Color.WHITE)) .build(); .build(); } } Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +20 −10 Original line number Original line Diff line number Diff line Loading @@ -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_ALIGNMENT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; 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_DISCLOSURE; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING; Loading Loading @@ -112,6 +113,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo", private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo", "bar"); "bar"); private static final int TEST_STRING_RES = R.string.keyguard_indication_trust_unlocked; private String mKeyguardTryFingerprintMsg; private String mKeyguardTryFingerprintMsg; private String mDisclosureWithOrganization; private String mDisclosureWithOrganization; private String mDisclosureGeneric; private String mDisclosureGeneric; Loading Loading @@ -419,7 +422,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN transient text is shown // WHEN transient text is shown mStatusBarStateListener.onDozingChanged(true); mStatusBarStateListener.onDozingChanged(true); mController.showTransientIndication("Test"); mController.showTransientIndication(TEST_STRING_RES); // THEN wake lock is held while the animation is running // THEN wake lock is held while the animation is running assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld()); assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld()); Loading @@ -434,7 +437,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // WHEN we show the transient indication // WHEN we show the transient indication mStatusBarStateListener.onDozingChanged(true); mStatusBarStateListener.onDozingChanged(true); mController.showTransientIndication("Test"); mController.showTransientIndication(TEST_STRING_RES); // THEN wake lock is RELEASED, not held // THEN wake lock is RELEASED, not held assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld()); assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld()); Loading @@ -445,10 +448,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { createController(); createController(); mController.setVisible(true); mController.setVisible(true); mController.showTransientIndication("Test"); mController.showTransientIndication(TEST_STRING_RES); mStatusBarStateListener.onDozingChanged(true); 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.getCurrentTextColor()).isEqualTo(Color.WHITE); assertThat(mTextView.getAlpha()).isEqualTo(1f); assertThat(mTextView.getAlpha()).isEqualTo(1f); } } Loading @@ -462,11 +466,11 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.getKeyguardCallback().onBiometricHelp( mController.getKeyguardCallback().onBiometricHelp( KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, message, BiometricSourceType.FACE); BiometricSourceType.FACE); verifyTransientMessage(message); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); reset(mRotateTextViewController); reset(mRotateTextViewController); mStatusBarStateListener.onDozingChanged(true); mStatusBarStateListener.onDozingChanged(true); verifyHideIndication(INDICATION_TYPE_TRANSIENT); verifyHideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE); } } @Test @Test Loading @@ -478,7 +482,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT, mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT, "A message", BiometricSourceType.FACE); "A message", BiometricSourceType.FACE); verifyTransientMessage(message); verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, message); mStatusBarStateListener.onDozingChanged(true); mStatusBarStateListener.onDozingChanged(true); assertThat(mTextView.getText()).isNotEqualTo(message); assertThat(mTextView.getText()).isNotEqualTo(message); Loading @@ -497,7 +501,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar", FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar", BiometricSourceType.FINGERPRINT); BiometricSourceType.FINGERPRINT); verifyNoTransientMessage(); verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE); verifyNoMessage(INDICATION_TYPE_TRANSIENT); } } @Test @Test Loading Loading @@ -757,7 +762,12 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verify(mRotateTextViewController).showTransient(eq(message)); verify(mRotateTextViewController).showTransient(eq(message)); } } private void verifyNoTransientMessage() { private void verifyNoMessage(int type) { verify(mRotateTextViewController, never()).showTransient(any()); if (type == INDICATION_TYPE_TRANSIENT) { verify(mRotateTextViewController, never()).showTransient(anyString()); } else { verify(mRotateTextViewController, never()).updateIndication(eq(type), anyObject(), anyBoolean()); } } } } }