Merge "Improve IME hiding animation when uncloked by password" into sc-dev am: 281be88301

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13816977

Change-Id: I6d19355004e719a6dca323c5c717feda83552c3b
This commit is contained in:
Ming-Shin Lu
2021-03-15 17:27:11 +00:00
committed by Automerger Merge Worker
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) { if (timeoutMs == 0) {
mMessageAreaController.setMessage(mView.getWrongPasswordStringId()); 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() { 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. * A Base class for all Keyguard password/pattern/pin related inputs.
*/ */
public abstract class KeyguardInputView extends LinearLayout { public abstract class KeyguardInputView extends LinearLayout {
private Runnable mOnFinishImeAnimationRunnable;
public KeyguardInputView(Context context) { public KeyguardInputView(Context context) {
super(context); super(context);
@@ -47,7 +48,8 @@ public abstract class KeyguardInputView extends LinearLayout {
abstract CharSequence getTitle(); abstract CharSequence getTitle();
void animateForIme(float interpolatedFraction) { void animateForIme(float interpolatedFraction, boolean appearingAnim) {
return;
} }
boolean disallowInterceptTouch(MotionEvent event) { 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; 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_DEVICE_ADMIN;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; 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_TIMEOUT;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; 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.content.Context;
import android.graphics.Insets;
import android.graphics.Rect; import android.graphics.Rect;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator; import android.view.animation.Interpolator;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.TextViewInputDisabler; import com.android.internal.widget.TextViewInputDisabler;
import com.android.systemui.Interpolators;
import com.android.systemui.R; import com.android.systemui.R;
/** /**
* Displays an alphanumeric (latin-1) key entry for the user to enter * 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 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 // A delay constant to be used in a workaround for the situation where InputMethodManagerService
// is not switched to the new user yet. // is not switched to the new user yet.
// TODO: Remove this by ensuring such a race condition never happens. // TODO: Remove this by ensuring such a race condition never happens.
@@ -150,9 +164,63 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
} }
@Override @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(); animate().cancel();
setAlpha(Math.max(interpolatedFraction, getAlpha())); setAlpha(appearingAnim
? Math.max(interpolatedFraction, getAlpha())
: 1 - interpolatedFraction);
} }
@Override @Override

View File

@@ -189,23 +189,22 @@ public class KeyguardPasswordViewController
return; return;
} }
if (wasDisabled) { if (wasDisabled) {
mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); showInput();
} }
} }
@Override @Override
public void onResume(int reason) { public void onResume(int reason) {
super.onResume(reason); super.onResume(reason);
mPasswordEntry.requestFocus();
if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
showInput(); showInput();
} }
} }
private void showInput() { private void showInput() {
mPasswordEntry.post(() -> { mView.post(() -> {
if (mPasswordEntry.isFocused() && mView.isShown()) { if (mView.isShown()) {
mPasswordEntry.requestFocus();
mInputMethodManager.showSoftInput( mInputMethodManager.showSoftInput(
mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
} }
@@ -214,7 +213,18 @@ public class KeyguardPasswordViewController
@Override @Override
public void onPause() { 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); mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
} }

View File

@@ -23,11 +23,9 @@ import static java.lang.Integer.max;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect; import android.graphics.Rect;
import android.provider.Settings; import android.provider.Settings;
import android.util.AttributeSet; import android.util.AttributeSet;
@@ -42,12 +40,9 @@ import android.view.ViewConfiguration;
import android.view.ViewPropertyAnimator; import android.view.ViewPropertyAnimator;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.DynamicAnimation; import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -130,6 +125,9 @@ public class KeyguardSecurityContainer extends FrameLayout {
WindowInsetsAnimation.Bounds bounds) { WindowInsetsAnimation.Bounds bounds) {
if (!mDisappearAnimRunning) { if (!mDisappearAnimRunning) {
beginJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR); beginJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
} else {
beginJankInstrument(
InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
} }
mSecurityViewFlipper.getBoundsOnScreen(mFinalBounds); mSecurityViewFlipper.getBoundsOnScreen(mFinalBounds);
return bounds; return bounds;
@@ -138,25 +136,28 @@ public class KeyguardSecurityContainer extends FrameLayout {
@Override @Override
public WindowInsets onProgress(WindowInsets windowInsets, public WindowInsets onProgress(WindowInsets windowInsets,
List<WindowInsetsAnimation> list) { List<WindowInsetsAnimation> list) {
if (mDisappearAnimRunning) { float start = mDisappearAnimRunning
mSecurityViewFlipper.setTranslationY( ? -(mFinalBounds.bottom - mInitialBounds.bottom)
mInitialBounds.bottom - mFinalBounds.bottom); : mInitialBounds.bottom - mFinalBounds.bottom;
} else { float end = mDisappearAnimRunning
int translationY = 0; ? -((mFinalBounds.bottom - mInitialBounds.bottom) * 0.75f)
float interpolatedFraction = 1f; : 0f;
for (WindowInsetsAnimation animation : list) { int translationY = 0;
if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) { float interpolatedFraction = 1f;
continue; for (WindowInsetsAnimation animation : list) {
} if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
interpolatedFraction = animation.getInterpolatedFraction(); continue;
final int paddingBottom = (int) MathUtils.lerp(
mInitialBounds.bottom - mFinalBounds.bottom, 0,
interpolatedFraction);
translationY += paddingBottom;
} }
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; return windowInsets;
} }
@@ -164,7 +165,10 @@ public class KeyguardSecurityContainer extends FrameLayout {
public void onEnd(WindowInsetsAnimation animation) { public void onEnd(WindowInsetsAnimation animation) {
if (!mDisappearAnimRunning) { if (!mDisappearAnimRunning) {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR); 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) { public void startDisappearAnimation(SecurityMode securitySelection) {
mDisappearAnimRunning = true; 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) { 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 * Translate the entire view, and optionally inform the wrapped view of the progress
* so it can animate with the parent. * 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); super.setTranslationY(translationY);
KeyguardInputView v = getSecurityView(); KeyguardInputView v = getSecurityView();
if (v != null) v.animateForIme(interpolatedFraction); if (v != null) v.animateForIme(interpolatedFraction, appearingAnim);
} }
@Override @Override

View File

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