Merge changes from topic "kyatoh-b221483132-tm-dev" into tm-dev
* changes: Do not cleanup IME window if show is requested. Notify IMMS synchronously on IME insets hidden.
This commit is contained in:
committed by
Android (Google) Code Review
commit
37893c2046
@@ -53,7 +53,6 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
|||||||
private static final int DO_APP_PRIVATE_COMMAND = 100;
|
private static final int DO_APP_PRIVATE_COMMAND = 100;
|
||||||
private static final int DO_FINISH_SESSION = 110;
|
private static final int DO_FINISH_SESSION = 110;
|
||||||
private static final int DO_VIEW_CLICKED = 115;
|
private static final int DO_VIEW_CLICKED = 115;
|
||||||
private static final int DO_NOTIFY_IME_HIDDEN = 120;
|
|
||||||
private static final int DO_REMOVE_IME_SURFACE = 130;
|
private static final int DO_REMOVE_IME_SURFACE = 130;
|
||||||
private static final int DO_FINISH_INPUT = 140;
|
private static final int DO_FINISH_INPUT = 140;
|
||||||
private static final int DO_INVALIDATE_INPUT = 150;
|
private static final int DO_INVALIDATE_INPUT = 150;
|
||||||
@@ -133,10 +132,6 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
|||||||
mInputMethodSession.viewClicked(msg.arg1 == 1);
|
mInputMethodSession.viewClicked(msg.arg1 == 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case DO_NOTIFY_IME_HIDDEN: {
|
|
||||||
mInputMethodSession.notifyImeHidden();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case DO_REMOVE_IME_SURFACE: {
|
case DO_REMOVE_IME_SURFACE: {
|
||||||
mInputMethodSession.removeImeSurface();
|
mInputMethodSession.removeImeSurface();
|
||||||
return;
|
return;
|
||||||
@@ -197,11 +192,6 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
|
|||||||
mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
|
mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyImeHidden() {
|
|
||||||
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_NOTIFY_IME_HIDDEN));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeImeSurface() {
|
public void removeImeSurface() {
|
||||||
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_IME_SURFACE));
|
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_IME_SURFACE));
|
||||||
|
|||||||
@@ -1058,10 +1058,6 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
return viewRoot == null ? null : viewRoot.getInputToken();
|
return viewRoot == null ? null : viewRoot.getInputToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyImeHidden() {
|
|
||||||
requestHideSelf(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scheduleImeSurfaceRemoval() {
|
private void scheduleImeSurfaceRemoval() {
|
||||||
if (mShowInputRequested || mWindowVisible || mWindow == null
|
if (mShowInputRequested || mWindowVisible || mWindow == null
|
||||||
|| mImeSurfaceScheduledForRemoval) {
|
|| mImeSurfaceScheduledForRemoval) {
|
||||||
@@ -1224,14 +1220,6 @@ public class InputMethodService extends AbstractInputMethodService {
|
|||||||
InputMethodService.this.onUpdateCursorAnchorInfo(info);
|
InputMethodService.this.onUpdateCursorAnchorInfo(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify IME that window is hidden.
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public final void notifyImeHidden() {
|
|
||||||
InputMethodService.this.notifyImeHidden();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify IME that surface can be now removed.
|
* Notify IME that surface can be now removed.
|
||||||
* @hide
|
* @hide
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ package android.view;
|
|||||||
|
|
||||||
import static android.os.Trace.TRACE_TAG_VIEW;
|
import static android.os.Trace.TRACE_TAG_VIEW;
|
||||||
import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
|
import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
|
||||||
|
import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING;
|
||||||
import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
|
import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
|
||||||
|
import static android.view.ImeInsetsSourceConsumerProto.IS_SHOW_REQUESTED_DURING_HIDE_ANIMATION;
|
||||||
import static android.view.InsetsController.AnimationType;
|
import static android.view.InsetsController.AnimationType;
|
||||||
import static android.view.InsetsState.ITYPE_IME;
|
import static android.view.InsetsState.ITYPE_IME;
|
||||||
|
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.inputmethodservice.InputMethodService;
|
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Trace;
|
import android.os.Trace;
|
||||||
import android.util.proto.ProtoOutputStream;
|
import android.util.proto.ProtoOutputStream;
|
||||||
@@ -44,6 +45,16 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
|
|||||||
*/
|
*/
|
||||||
private boolean mIsRequestedVisibleAwaitingControl;
|
private boolean mIsRequestedVisibleAwaitingControl;
|
||||||
|
|
||||||
|
private boolean mIsHideAnimationRunning;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks whether {@link WindowInsetsController#show(int)} or
|
||||||
|
* {@link InputMethodManager#showSoftInput(View, int)} is called during IME hide animation.
|
||||||
|
* If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder)},
|
||||||
|
* because the IME is being shown.
|
||||||
|
*/
|
||||||
|
private boolean mIsShowRequestedDuringHideAnimation;
|
||||||
|
|
||||||
public ImeInsetsSourceConsumer(
|
public ImeInsetsSourceConsumer(
|
||||||
InsetsState state, Supplier<Transaction> transactionSupplier,
|
InsetsState state, Supplier<Transaction> transactionSupplier,
|
||||||
InsetsController controller) {
|
InsetsController controller) {
|
||||||
@@ -63,6 +74,12 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
|
|||||||
mIsRequestedVisibleAwaitingControl = false;
|
mIsRequestedVisibleAwaitingControl = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show(boolean fromIme) {
|
||||||
|
super.show(fromIme);
|
||||||
|
onShowRequested();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hide() {
|
public void hide() {
|
||||||
super.hide();
|
super.hide();
|
||||||
@@ -74,11 +91,21 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
|
|||||||
hide();
|
hide();
|
||||||
|
|
||||||
if (animationFinished) {
|
if (animationFinished) {
|
||||||
// remove IME surface as IME has finished hide animation.
|
// Remove IME surface as IME has finished hide animation, if there is no pending
|
||||||
|
// show request.
|
||||||
|
if (!mIsShowRequestedDuringHideAnimation) {
|
||||||
notifyHidden();
|
notifyHidden();
|
||||||
removeSurface();
|
removeSurface();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// This method is called
|
||||||
|
// (1) before the hide animation starts.
|
||||||
|
// (2) after the hide animation ends.
|
||||||
|
// (3) if the IME is not controllable (animationFinished == true in this case).
|
||||||
|
// We should reset mIsShowRequestedDuringHideAnimation in all cases.
|
||||||
|
mIsHideAnimationRunning = !animationFinished;
|
||||||
|
mIsShowRequestedDuringHideAnimation = false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request {@link InputMethodManager} to show the IME.
|
* Request {@link InputMethodManager} to show the IME.
|
||||||
@@ -104,7 +131,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify {@link InputMethodService} that IME window is hidden.
|
* Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
|
||||||
|
* IME insets are hidden.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
void notifyHidden() {
|
void notifyHidden() {
|
||||||
@@ -157,9 +185,20 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
|
|||||||
final long token = proto.start(fieldId);
|
final long token = proto.start(fieldId);
|
||||||
super.dumpDebug(proto, INSETS_SOURCE_CONSUMER);
|
super.dumpDebug(proto, INSETS_SOURCE_CONSUMER);
|
||||||
proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl);
|
proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl);
|
||||||
|
proto.write(IS_HIDE_ANIMATION_RUNNING, mIsHideAnimationRunning);
|
||||||
|
proto.write(IS_SHOW_REQUESTED_DURING_HIDE_ANIMATION, mIsShowRequestedDuringHideAnimation);
|
||||||
proto.end(token);
|
proto.end(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when {@link #show} or {@link InputMethodManager#showSoftInput(View, int)} is called.
|
||||||
|
*/
|
||||||
|
public void onShowRequested() {
|
||||||
|
if (mIsHideAnimationRunning) {
|
||||||
|
mIsShowRequestedDuringHideAnimation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private InputMethodManager getImm() {
|
private InputMethodManager getImm() {
|
||||||
return mController.getHost().getInputMethodManager();
|
return mController.getHost().getInputMethodManager();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ public final class InputMethodManager {
|
|||||||
static final int MSG_BIND_ACCESSIBILITY_SERVICE = 11;
|
static final int MSG_BIND_ACCESSIBILITY_SERVICE = 11;
|
||||||
static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 12;
|
static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 12;
|
||||||
static final int MSG_UPDATE_VIRTUAL_DISPLAY_TO_SCREEN_MATRIX = 30;
|
static final int MSG_UPDATE_VIRTUAL_DISPLAY_TO_SCREEN_MATRIX = 30;
|
||||||
|
static final int MSG_ON_SHOW_REQUESTED = 31;
|
||||||
|
|
||||||
private static boolean isAutofillUIShowing(View servedView) {
|
private static boolean isAutofillUIShowing(View servedView) {
|
||||||
AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
|
AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
|
||||||
@@ -1117,6 +1118,14 @@ public final class InputMethodManager {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case MSG_ON_SHOW_REQUESTED: {
|
||||||
|
synchronized (mH) {
|
||||||
|
if (mImeInsetsConsumer != null) {
|
||||||
|
mImeInsetsConsumer.onShowRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1834,6 +1843,9 @@ public final class InputMethodManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
|
||||||
|
// TODO(b/229426865): call WindowInsetsController#show instead.
|
||||||
|
mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
|
Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
|
||||||
+ InputMethodDebug.softInputDisplayReasonToString(reason));
|
+ InputMethodDebug.softInputDisplayReasonToString(reason));
|
||||||
@@ -1869,6 +1881,9 @@ public final class InputMethodManager {
|
|||||||
Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
|
Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
|
||||||
|
// TODO(b/229426865): call WindowInsetsController#show instead.
|
||||||
|
mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
|
||||||
mService.showSoftInput(
|
mService.showSoftInput(
|
||||||
mClient,
|
mClient,
|
||||||
mCurRootView.getView().getWindowToken(),
|
mCurRootView.getView().getWindowToken(),
|
||||||
@@ -2521,7 +2536,7 @@ public final class InputMethodManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify IME directly that it is no longer visible.
|
* Notify IMMS that IME insets are no longer visible.
|
||||||
*
|
*
|
||||||
* @param windowToken the window from which this request originates. If this doesn't match the
|
* @param windowToken the window from which this request originates. If this doesn't match the
|
||||||
* currently served view, the request is ignored.
|
* currently served view, the request is ignored.
|
||||||
@@ -2533,7 +2548,13 @@ public final class InputMethodManager {
|
|||||||
synchronized (mH) {
|
synchronized (mH) {
|
||||||
if (mCurrentInputMethodSession != null && mCurRootView != null
|
if (mCurrentInputMethodSession != null && mCurRootView != null
|
||||||
&& mCurRootView.getWindowToken() == windowToken) {
|
&& mCurRootView.getWindowToken() == windowToken) {
|
||||||
mCurrentInputMethodSession.notifyImeHidden();
|
try {
|
||||||
|
mService.hideSoftInput(mClient, windowToken, 0 /* flags */,
|
||||||
|
null /* resultReceiver */,
|
||||||
|
SoftInputShowHideReason.HIDE_SOFT_INPUT);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw e.rethrowFromSystemServer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,13 +194,6 @@ public interface InputMethodSession {
|
|||||||
*/
|
*/
|
||||||
public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo);
|
public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo);
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies {@link android.inputmethodservice.InputMethodService} that IME has been
|
|
||||||
* hidden from user.
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public void notifyImeHidden();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify IME directly to remove surface as it is no longer visible.
|
* Notify IME directly to remove surface as it is no longer visible.
|
||||||
* @hide
|
* @hide
|
||||||
|
|||||||
@@ -105,15 +105,6 @@ final class InputMethodSessionWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@AnyThread
|
|
||||||
void notifyImeHidden() {
|
|
||||||
try {
|
|
||||||
mSession.notifyImeHidden();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Log.w(TAG, "IME died", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
void viewClicked(boolean focusChanged) {
|
void viewClicked(boolean focusChanged) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ oneway interface IInputMethodSession {
|
|||||||
|
|
||||||
void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo);
|
void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo);
|
||||||
|
|
||||||
void notifyImeHidden();
|
|
||||||
|
|
||||||
void removeImeSurface();
|
void removeImeSurface();
|
||||||
|
|
||||||
void finishInput();
|
void finishInput();
|
||||||
|
|||||||
@@ -29,4 +29,6 @@ message ImeInsetsSourceConsumerProto {
|
|||||||
optional InsetsSourceConsumerProto insets_source_consumer = 1;
|
optional InsetsSourceConsumerProto insets_source_consumer = 1;
|
||||||
reserved 2; // focused_editor = 2
|
reserved 2; // focused_editor = 2
|
||||||
optional bool is_requested_visible_awaiting_control = 3;
|
optional bool is_requested_visible_awaiting_control = 3;
|
||||||
|
optional bool is_hide_animation_running = 4;
|
||||||
|
optional bool is_show_requested_during_hide_animation = 5;
|
||||||
}
|
}
|
||||||
@@ -30,6 +30,7 @@ import android.os.Bundle;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.platform.test.annotations.RootPermissionTest;
|
import android.platform.test.annotations.RootPermissionTest;
|
||||||
import android.platform.test.rule.UnlockScreenRule;
|
import android.platform.test.rule.UnlockScreenRule;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.view.WindowInsetsAnimation;
|
import android.view.WindowInsetsAnimation;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
@@ -40,16 +41,19 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RootPermissionTest
|
@RootPermissionTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public final class ImeOpenCloseStressTest {
|
public final class ImeOpenCloseStressTest {
|
||||||
|
|
||||||
|
private static final String TAG = "ImeOpenCloseStressTest";
|
||||||
private static final int NUM_TEST_ITERATIONS = 10;
|
private static final int NUM_TEST_ITERATIONS = 10;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
@@ -58,32 +62,103 @@ public final class ImeOpenCloseStressTest {
|
|||||||
@Rule
|
@Rule
|
||||||
public ScreenCaptureRule mScreenCaptureRule =
|
public ScreenCaptureRule mScreenCaptureRule =
|
||||||
new ScreenCaptureRule("/sdcard/InputMethodStressTest");
|
new ScreenCaptureRule("/sdcard/InputMethodStressTest");
|
||||||
|
private Instrumentation mInstrumentation;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
mInstrumentation = InstrumentationRegistry.getInstrumentation();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void testShowHide_waitingVisibilityChange() {
|
||||||
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
TestActivity activity = TestActivity.start();
|
||||||
Intent intent = new Intent()
|
|
||||||
.setAction(Intent.ACTION_MAIN)
|
|
||||||
.setClass(instrumentation.getContext(), TestActivity.class)
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
|
|
||||||
EditText editText = activity.getEditText();
|
EditText editText = activity.getEditText();
|
||||||
waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
|
waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
|
||||||
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
|
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
|
||||||
String msgPrefix = "Iteration #" + i + " ";
|
String msgPrefix = "Iteration #" + i + " ";
|
||||||
instrumentation.runOnMainSync(activity::showIme);
|
Log.i(TAG, msgPrefix + "start");
|
||||||
|
mInstrumentation.runOnMainSync(activity::showIme);
|
||||||
|
waitOnMainUntil(msgPrefix + "IME should be visible", () -> isImeShown(editText));
|
||||||
|
mInstrumentation.runOnMainSync(activity::hideIme);
|
||||||
|
waitOnMainUntil(msgPrefix + "IME should be hidden", () -> !isImeShown(editText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShowHide_waitingAnimationEnd() {
|
||||||
|
TestActivity activity = TestActivity.start();
|
||||||
|
activity.enableAnimationMonitoring();
|
||||||
|
EditText editText = activity.getEditText();
|
||||||
|
waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
|
||||||
|
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
|
||||||
|
String msgPrefix = "Iteration #" + i + " ";
|
||||||
|
Log.i(TAG, msgPrefix + "start");
|
||||||
|
mInstrumentation.runOnMainSync(activity::showIme);
|
||||||
waitOnMainUntil(msgPrefix + "IME should be visible",
|
waitOnMainUntil(msgPrefix + "IME should be visible",
|
||||||
() -> !activity.isAnimating() && isImeShown(editText));
|
() -> !activity.isAnimating() && isImeShown(editText));
|
||||||
instrumentation.runOnMainSync(activity::hideIme);
|
mInstrumentation.runOnMainSync(activity::hideIme);
|
||||||
waitOnMainUntil(msgPrefix + "IME should be hidden",
|
waitOnMainUntil(msgPrefix + "IME should be hidden",
|
||||||
() -> !activity.isAnimating() && !isImeShown(editText));
|
() -> !activity.isAnimating() && !isImeShown(editText));
|
||||||
// b/b/221483132, wait until IMS and IMMS handles IMM#notifyImeHidden.
|
|
||||||
// There is no good signal, so we just wait a second.
|
|
||||||
SystemClock.sleep(1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShowHide_intervalAfterHide() {
|
||||||
|
// Regression test for b/221483132
|
||||||
|
TestActivity activity = TestActivity.start();
|
||||||
|
EditText editText = activity.getEditText();
|
||||||
|
// Intervals = 10, 20, 30, ..., 100, 150, 200, ...
|
||||||
|
List<Integer> intervals = new ArrayList<>();
|
||||||
|
for (int i = 10; i < 100; i += 10) intervals.add(i);
|
||||||
|
for (int i = 100; i < 1000; i += 50) intervals.add(i);
|
||||||
|
waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
|
||||||
|
for (int intervalMillis : intervals) {
|
||||||
|
String msgPrefix = "Interval = " + intervalMillis + " ";
|
||||||
|
Log.i(TAG, msgPrefix + " start");
|
||||||
|
mInstrumentation.runOnMainSync(activity::hideIme);
|
||||||
|
SystemClock.sleep(intervalMillis);
|
||||||
|
mInstrumentation.runOnMainSync(activity::showIme);
|
||||||
|
waitOnMainUntil(msgPrefix + "IME should be visible",
|
||||||
|
() -> isImeShown(editText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShowHideInSameFrame() {
|
||||||
|
TestActivity activity = TestActivity.start();
|
||||||
|
activity.enableAnimationMonitoring();
|
||||||
|
EditText editText = activity.getEditText();
|
||||||
|
waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
|
||||||
|
|
||||||
|
// hidden -> show -> hide
|
||||||
|
mInstrumentation.runOnMainSync(() -> {
|
||||||
|
Log.i(TAG, "Calling showIme() and hideIme()");
|
||||||
|
activity.showIme();
|
||||||
|
activity.hideIme();
|
||||||
|
});
|
||||||
|
// Wait until IMMS / IMS handles messages.
|
||||||
|
SystemClock.sleep(1000);
|
||||||
|
mInstrumentation.waitForIdleSync();
|
||||||
|
waitOnMainUntil("IME should be invisible after show/hide", () -> !isImeShown(editText));
|
||||||
|
|
||||||
|
mInstrumentation.runOnMainSync(activity::showIme);
|
||||||
|
waitOnMainUntil("IME should be visible",
|
||||||
|
() -> !activity.isAnimating() && isImeShown(editText));
|
||||||
|
mInstrumentation.waitForIdleSync();
|
||||||
|
|
||||||
|
// shown -> hide -> show
|
||||||
|
mInstrumentation.runOnMainSync(() -> {
|
||||||
|
Log.i(TAG, "Calling hideIme() and showIme()");
|
||||||
|
activity.hideIme();
|
||||||
|
activity.showIme();
|
||||||
|
});
|
||||||
|
// Wait until IMMS / IMS handles messages.
|
||||||
|
SystemClock.sleep(1000);
|
||||||
|
mInstrumentation.waitForIdleSync();
|
||||||
|
waitOnMainUntil("IME should be visible after hide/show",
|
||||||
|
() -> !activity.isAnimating() && isImeShown(editText));
|
||||||
|
}
|
||||||
|
|
||||||
public static class TestActivity extends Activity {
|
public static class TestActivity extends Activity {
|
||||||
|
|
||||||
private EditText mEditText;
|
private EditText mEditText;
|
||||||
@@ -111,6 +186,15 @@ public final class ImeOpenCloseStressTest {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static TestActivity start() {
|
||||||
|
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
||||||
|
Intent intent = new Intent()
|
||||||
|
.setAction(Intent.ACTION_MAIN)
|
||||||
|
.setClass(instrumentation.getContext(), TestActivity.class)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
return (TestActivity) instrumentation.startActivitySync(intent);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
@@ -120,9 +204,6 @@ public final class ImeOpenCloseStressTest {
|
|||||||
mEditText = new EditText(this);
|
mEditText = new EditText(this);
|
||||||
rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
|
rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
|
||||||
setContentView(rootView);
|
setContentView(rootView);
|
||||||
// Enable WindowInsetsAnimation.
|
|
||||||
getWindow().setDecorFitsSystemWindows(false);
|
|
||||||
mEditText.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public EditText getEditText() {
|
public EditText getEditText() {
|
||||||
@@ -130,16 +211,27 @@ public final class ImeOpenCloseStressTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void showIme() {
|
public void showIme() {
|
||||||
|
Log.i(TAG, "TestActivity.showIme");
|
||||||
mEditText.requestFocus();
|
mEditText.requestFocus();
|
||||||
InputMethodManager imm = getSystemService(InputMethodManager.class);
|
InputMethodManager imm = getSystemService(InputMethodManager.class);
|
||||||
imm.showSoftInput(mEditText, 0);
|
imm.showSoftInput(mEditText, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hideIme() {
|
public void hideIme() {
|
||||||
|
Log.i(TAG, "TestActivity.hideIme");
|
||||||
InputMethodManager imm = getSystemService(InputMethodManager.class);
|
InputMethodManager imm = getSystemService(InputMethodManager.class);
|
||||||
imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
|
imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void enableAnimationMonitoring() {
|
||||||
|
// Enable WindowInsetsAnimation.
|
||||||
|
// Note that this has a side effect of disabling InsetsAnimationThreadControlRunner.
|
||||||
|
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
|
||||||
|
getWindow().setDecorFitsSystemWindows(false);
|
||||||
|
mEditText.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAnimating() {
|
public boolean isAnimating() {
|
||||||
return mIsAnimating;
|
return mIsAnimating;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user