Merge "Improve IME hiding animation when uncloked by password" into sc-dev

This commit is contained in:
Ming-Shin Lu
2021-03-15 17:03:15 +00:00
committed by Android (Google) Code Review
8 changed files with 156 additions and 107 deletions

View File

@@ -185,8 +185,8 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
if (timeoutMs == 0) {
mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
}
mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
}
mView.resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
}
protected void verifyPasswordAndUnlock() {

View File

@@ -31,6 +31,7 @@ import com.android.internal.jank.InteractionJankMonitor;
* A Base class for all Keyguard password/pattern/pin related inputs.
*/
public abstract class KeyguardInputView extends LinearLayout {
private Runnable mOnFinishImeAnimationRunnable;
public KeyguardInputView(Context context) {
super(context);
@@ -47,7 +48,8 @@ public abstract class KeyguardInputView extends LinearLayout {
abstract CharSequence getTitle();
void animateForIme(float interpolatedFraction) {
void animateForIme(float interpolatedFraction, boolean appearingAnim) {
return;
}
boolean disallowInterceptTouch(MotionEvent event) {
@@ -85,4 +87,14 @@ public abstract class KeyguardInputView extends LinearLayout {
};
}
public void setOnFinishImeAnimationRunnable(Runnable onFinishImeAnimationRunnable) {
mOnFinishImeAnimationRunnable = onFinishImeAnimationRunnable;
}
public void runOnFinishImeAnimationRunnable() {
if (mOnFinishImeAnimationRunnable != null) {
mOnFinishImeAnimationRunnable.run();
mOnFinishImeAnimationRunnable = null;
}
}
}

View File

@@ -16,6 +16,8 @@
package com.android.keyguard;
import static android.view.WindowInsets.Type.ime;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
@@ -23,15 +25,25 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.TextViewInputDisabler;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
/**
* Displays an alphanumeric (latin-1) key entry for the user to enter
@@ -41,6 +53,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
private final int mDisappearYTranslation;
private static final long IME_DISAPPEAR_DURATION_MS = 125;
// A delay constant to be used in a workaround for the situation where InputMethodManagerService
// is not switched to the new user yet.
// TODO: Remove this by ensuring such a race condition never happens.
@@ -150,9 +164,63 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
}
@Override
public void animateForIme(float interpolatedFraction) {
public boolean startDisappearAnimation(Runnable finishRunnable) {
getWindowInsetsController().controlWindowInsetsAnimation(ime(),
100,
Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
@Override
public void onReady(@NonNull WindowInsetsAnimationController controller,
int types) {
ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
anim.addUpdateListener(animation -> {
if (controller.isCancelled()) {
return;
}
Insets shownInsets = controller.getShownStateInsets();
Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0,
(int) (-shownInsets.bottom / 4
* anim.getAnimatedFraction())));
controller.setInsetsAndAlpha(insets,
(float) animation.getAnimatedValue(),
anim.getAnimatedFraction());
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
controller.finish(false);
runOnFinishImeAnimationRunnable();
finishRunnable.run();
}
});
anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
anim.start();
}
@Override
public void onFinished(
@NonNull WindowInsetsAnimationController controller) {
}
@Override
public void onCancelled(
@Nullable WindowInsetsAnimationController controller) {
}
});
return true;
}
@Override
public void animateForIme(float interpolatedFraction, boolean appearingAnim) {
animate().cancel();
setAlpha(Math.max(interpolatedFraction, getAlpha()));
setAlpha(appearingAnim
? Math.max(interpolatedFraction, getAlpha())
: 1 - interpolatedFraction);
}
@Override

View File

@@ -189,23 +189,22 @@ public class KeyguardPasswordViewController
return;
}
if (wasDisabled) {
mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
showInput();
}
}
@Override
public void onResume(int reason) {
super.onResume(reason);
mPasswordEntry.requestFocus();
if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
showInput();
}
}
private void showInput() {
mPasswordEntry.post(() -> {
if (mPasswordEntry.isFocused() && mView.isShown()) {
mView.post(() -> {
if (mView.isShown()) {
mPasswordEntry.requestFocus();
mInputMethodManager.showSoftInput(
mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
}
@@ -214,7 +213,18 @@ public class KeyguardPasswordViewController
@Override
public void onPause() {
super.onPause();
if (!mPasswordEntry.isVisibleToUser()) {
// Reset all states directly and then hide IME when the screen turned off.
super.onPause();
} else {
// In order not to break the IME hide animation by resetting states too early after
// the password checked, make sure resetting states after the IME hiding animation
// finished.
mView.setOnFinishImeAnimationRunnable(() -> {
mPasswordEntry.clearFocus();
super.onPause();
});
}
mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
}

View File

@@ -23,11 +23,9 @@ import static java.lang.Integer.max;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
import android.provider.Settings;
import android.util.AttributeSet;
@@ -42,12 +40,9 @@ import android.view.ViewConfiguration;
import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -130,6 +125,9 @@ public class KeyguardSecurityContainer extends FrameLayout {
WindowInsetsAnimation.Bounds bounds) {
if (!mDisappearAnimRunning) {
beginJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
} else {
beginJankInstrument(
InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
}
mSecurityViewFlipper.getBoundsOnScreen(mFinalBounds);
return bounds;
@@ -138,25 +136,28 @@ public class KeyguardSecurityContainer extends FrameLayout {
@Override
public WindowInsets onProgress(WindowInsets windowInsets,
List<WindowInsetsAnimation> list) {
if (mDisappearAnimRunning) {
mSecurityViewFlipper.setTranslationY(
mInitialBounds.bottom - mFinalBounds.bottom);
} else {
int translationY = 0;
float interpolatedFraction = 1f;
for (WindowInsetsAnimation animation : list) {
if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
continue;
}
interpolatedFraction = animation.getInterpolatedFraction();
final int paddingBottom = (int) MathUtils.lerp(
mInitialBounds.bottom - mFinalBounds.bottom, 0,
interpolatedFraction);
translationY += paddingBottom;
float start = mDisappearAnimRunning
? -(mFinalBounds.bottom - mInitialBounds.bottom)
: mInitialBounds.bottom - mFinalBounds.bottom;
float end = mDisappearAnimRunning
? -((mFinalBounds.bottom - mInitialBounds.bottom) * 0.75f)
: 0f;
int translationY = 0;
float interpolatedFraction = 1f;
for (WindowInsetsAnimation animation : list) {
if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
continue;
}
mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction);
interpolatedFraction = animation.getInterpolatedFraction();
final int paddingBottom = (int) MathUtils.lerp(
start, end,
interpolatedFraction);
translationY += paddingBottom;
}
mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction,
!mDisappearAnimRunning);
return windowInsets;
}
@@ -164,7 +165,10 @@ public class KeyguardSecurityContainer extends FrameLayout {
public void onEnd(WindowInsetsAnimation animation) {
if (!mDisappearAnimRunning) {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f);
mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f,
true /* appearingAnim */);
} else {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
}
}
};
@@ -522,63 +526,6 @@ public class KeyguardSecurityContainer extends FrameLayout {
public void startDisappearAnimation(SecurityMode securitySelection) {
mDisappearAnimRunning = true;
if (securitySelection == SecurityMode.Password) {
mSecurityViewFlipper.getWindowInsetsController().controlWindowInsetsAnimation(ime(),
IME_DISAPPEAR_DURATION_MS,
Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
@Override
public void onReady(@NonNull WindowInsetsAnimationController controller,
int types) {
ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
anim.addUpdateListener(animation -> {
if (controller.isCancelled()) {
return;
}
Insets shownInsets = controller.getShownStateInsets();
Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0,
(int) (-shownInsets.bottom / 4
* anim.getAnimatedFraction())));
controller.setInsetsAndAlpha(insets,
(float) animation.getAnimatedValue(),
anim.getAnimatedFraction());
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
beginJankInstrument(
InteractionJankMonitor
.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
}
@Override
public void onAnimationEnd(Animator animation) {
endJankInstrument(
InteractionJankMonitor
.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
controller.finish(false);
}
});
anim.setDuration(IME_DISAPPEAR_DURATION_MS);
anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
anim.start();
}
@Override
public void onFinished(
@NonNull WindowInsetsAnimationController controller) {
mDisappearAnimRunning = false;
}
@Override
public void onCancelled(
@Nullable WindowInsetsAnimationController controller) {
cancelJankInstrument(
InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
}
});
}
}
private void beginJankInstrument(int cuj) {

View File

@@ -87,10 +87,10 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper {
* Translate the entire view, and optionally inform the wrapped view of the progress
* so it can animate with the parent.
*/
public void animateForIme(int translationY, float interpolatedFraction) {
public void animateForIme(int translationY, float interpolatedFraction, boolean appearingAnim) {
super.setTranslationY(translationY);
KeyguardInputView v = getSecurityView();
if (v != null) v.animateForIme(interpolatedFraction);
if (v != null) v.animateForIme(interpolatedFraction, appearingAnim);
}
@Override

View File

@@ -16,13 +16,19 @@
package com.android.keyguard;
import static android.view.WindowInsets.Type.ime;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowInsetsController;
@@ -33,6 +39,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.FalsingManager;
@@ -84,18 +91,34 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
@Mock
private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
@Mock
private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
@Mock
private KeyguardMessageArea mKeyguardMessageArea;
@Mock
private ConfigurationController mConfigurationController;
@Mock
private KeyguardViewController mKeyguardViewController;
private FalsingManager mFalsingManager = new FalsingManagerFake();
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
private KeyguardPasswordViewController mKeyguardPasswordViewController;
private KeyguardPasswordView mKeyguardPasswordView;
@Before
public void setup() {
when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
.thenReturn(mAdminSecondaryLockScreenController);
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
mKeyguardPasswordView = spy(new KeyguardPasswordView(getContext()));
when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper);
when(mKeyguardPasswordView.findViewById(R.id.keyguard_message_area))
.thenReturn(mKeyguardMessageArea);
when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
mKeyguardPasswordViewController = new KeyguardPasswordViewController(
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
SecurityMode.Password, mLockPatternUtils, null,
mKeyguardMessageAreaControllerFactory, null, null, null, mock(Resources.class),
null);
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
@@ -125,14 +148,13 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
public void startDisappearAnimation_animatesKeyboard() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
SecurityMode.Password);
when(mInputViewController.getSecurityMode()).thenReturn(
SecurityMode.Password);
when(mKeyguardSecurityViewFlipperController.getSecurityView(
eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
.thenReturn(mInputViewController);
mKeyguardSecurityContainerController.showPrimarySecurityScreen(false /* turningOff */);
.thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
mKeyguardSecurityContainerController.startDisappearAnimation(null);
verify(mInputViewController).startDisappearAnimation(eq(null));
verify(mWindowInsetsController).controlWindowInsetsAnimation(
eq(ime()), anyLong(), any(), any(), any());
}
}

View File

@@ -19,9 +19,6 @@ package com.android.keyguard;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -89,13 +86,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
@Test
public void startDisappearAnimation_animatesKeyboard() {
mKeyguardSecurityContainer.startDisappearAnimation(SecurityMode.Password);
verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(),
any(), any());
}
@Test
public void onMeasure_usesFullWidthWithoutOneHandedMode() {
setUpKeyguard(