From ab008846a8eea36f02a7762d65ca233004929b47 Mon Sep 17 00:00:00 2001 From: Bernardo Rufino Date: Tue, 17 Nov 2020 18:33:01 +0000 Subject: [PATCH] Optionally wait animations on syncInputTransactions/injectInputEvent I need to perform a touch while animating in CTS to test ag/13067140, but current methods all wait for animations to complete, which defeats the purpose. Creating new methods that optionally wait for animations to complete. Test: atest WindowUntrustedTouchTest on child CL Bug: 172787052 Change-Id: I4832ea2c62531a73098d2817930638ccb5f40765 --- core/api/test-current.txt | 2 + .../android/app/IUiAutomationConnection.aidl | 4 +- core/java/android/app/Instrumentation.java | 3 +- core/java/android/app/UiAutomation.java | 50 +++++++++++++++++-- .../android/app/UiAutomationConnection.java | 10 ++-- core/java/android/view/IWindowManager.aidl | 14 +++--- .../server/wm/WindowManagerService.java | 15 +++--- 7 files changed, 75 insertions(+), 23 deletions(-) diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f695adf3e01ca..38918660f0f91 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -302,8 +302,10 @@ package android.app { method public void destroy(); method @NonNull public android.os.ParcelFileDescriptor[] executeShellCommandRwe(@NonNull String); method @Deprecated public boolean grantRuntimePermission(String, String, android.os.UserHandle); + method public boolean injectInputEvent(@NonNull android.view.InputEvent, boolean, boolean); method @Deprecated public boolean revokeRuntimePermission(String, String, android.os.UserHandle); method public void syncInputTransactions(); + method public void syncInputTransactions(boolean); } public class UiModeManager { diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl index 9eeb9f6a95bf8..ec7d7832dc820 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -36,8 +36,8 @@ import android.os.ParcelFileDescriptor; interface IUiAutomationConnection { void connect(IAccessibilityServiceClient client, int flags); void disconnect(); - boolean injectInputEvent(in InputEvent event, boolean sync); - void syncInputTransactions(); + boolean injectInputEvent(in InputEvent event, boolean sync, boolean waitForAnimations); + void syncInputTransactions(boolean waitForAnimations); boolean setRotation(int rotation); Bitmap takeScreenshot(in Rect crop); boolean clearWindowContentFrameStats(int windowId); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 3e249bb24dd6b..e9d63d2bc7882 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1119,7 +1119,8 @@ public class Instrumentation { } try { WindowManagerGlobal.getWindowManagerService().injectInputAfterTransactionsApplied(event, - InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); + InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH, + true /* waitForAnimations */); } catch (RemoteException e) { } } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 1b0fd9edf4f8f..787393ed0f6ce 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -695,6 +695,9 @@ public final class UiAutomation { /** * A method for injecting an arbitrary input event. + * + * This method waits for all window container animations and surface operations to complete. + * *

* Note: It is caller's responsibility to recycle the event. *

@@ -704,12 +707,34 @@ public final class UiAutomation { * @return Whether event injection succeeded. */ public boolean injectInputEvent(InputEvent event, boolean sync) { + return injectInputEvent(event, sync, true /* waitForAnimations */); + } + + /** + * A method for injecting an arbitrary input event, optionally waiting for window animations to + * complete. + *

+ * Note: It is caller's responsibility to recycle the event. + *

+ * + * @param event The event to inject. + * @param sync Whether to inject the event synchronously. + * @param waitForAnimations Whether to wait for all window container animations and surface + * operations to complete. + * @return Whether event injection succeeded. + * + * @hide + */ + @TestApi + public boolean injectInputEvent(@NonNull InputEvent event, boolean sync, + boolean waitForAnimations) { try { if (DEBUG) { - Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync); + Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync + " waitForAnimations: " + + waitForAnimations); } // Calling out without a lock held. - return mUiAutomationConnection.injectInputEvent(event, sync); + return mUiAutomationConnection.injectInputEvent(event, sync, waitForAnimations); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while injecting input event!", re); } @@ -726,7 +751,26 @@ public final class UiAutomation { public void syncInputTransactions() { try { // Calling out without a lock held. - mUiAutomationConnection.syncInputTransactions(); + mUiAutomationConnection.syncInputTransactions(true /* waitForAnimations */); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while syncing input transactions!", re); + } + } + + /** + * A request for WindowManagerService to wait until all input information has been sent from + * WindowManager to native InputManager and optionally wait for animations to complete. + * + * @param waitForAnimations Whether to wait for all window container animations and surface + * operations to complete. + * + * @hide + */ + @TestApi + public void syncInputTransactions(boolean waitForAnimations) { + try { + // Calling out without a lock held. + mUiAutomationConnection.syncInputTransactions(waitForAnimations); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while syncing input transactions!", re); } diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 290e12191de86..7036b6e7dbc94 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -124,7 +124,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } @Override - public boolean injectInputEvent(InputEvent event, boolean sync) { + public boolean injectInputEvent(InputEvent event, boolean sync, boolean waitForAnimations) { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); @@ -134,7 +134,8 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC; final long identity = Binder.clearCallingIdentity(); try { - return mWindowManager.injectInputAfterTransactionsApplied(event, mode); + return mWindowManager.injectInputAfterTransactionsApplied(event, mode, + waitForAnimations); } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(identity); @@ -143,7 +144,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } @Override - public void syncInputTransactions() { + public void syncInputTransactions(boolean waitForAnimations) { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); @@ -151,12 +152,11 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } try { - mWindowManager.syncInputTransactions(); + mWindowManager.syncInputTransactions(waitForAnimations); } catch (RemoteException e) { } } - @Override public boolean setRotation(int rotation) { synchronized (mLock) { diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 924fc6d6dca01..053353330e4e7 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -668,22 +668,24 @@ interface IWindowManager void setShouldShowIme(int displayId, boolean shouldShow); /** - * Waits for transactions to get applied before injecting input. - * This includes waiting for the input windows to get sent to InputManager. + * Waits for transactions to get applied before injecting input, optionally waiting for + * animations to complete. This includes waiting for the input windows to get sent to + * InputManager. * * This is needed for testing since the system add windows and injects input * quick enough that the windows don't have time to get sent to InputManager. */ - boolean injectInputAfterTransactionsApplied(in InputEvent ev, int mode); + boolean injectInputAfterTransactionsApplied(in InputEvent ev, int mode, + boolean waitForAnimations); /** - * Waits until all animations have completed and input information has been sent from - * WindowManager to native InputManager. + * Waits until input information has been sent from WindowManager to native InputManager, + * optionally waiting for animations to complete. * * This is needed for testing since we need to ensure input information has been propagated to * native InputManager before proceeding with tests. */ - void syncInputTransactions(); + void syncInputTransactions(boolean waitForAnimations); /** * Returns whether SurfaceFlinger layer tracing is enabled. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 782d6e39bf538..b7bf31068e998 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2998,7 +2998,7 @@ public class WindowManagerService extends IWindowManager.Stub displayContent, true /* includingParents */); } } - syncInputTransactions(); + syncInputTransactions(true /* waitForAnimations */); } /** @@ -7999,7 +7999,8 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode) { + public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode, + boolean waitForAnimations) { boolean isDown; boolean isUp; @@ -8018,21 +8019,23 @@ public class WindowManagerService extends IWindowManager.Stub // For all mouse events, also sync before injecting. // For ACTION_UP, sync after injecting. if (isDown || isMouseEvent) { - syncInputTransactions(); + syncInputTransactions(waitForAnimations); } final boolean result = LocalServices.getService(InputManagerInternal.class).injectInputEvent(ev, mode); if (isUp) { - syncInputTransactions(); + syncInputTransactions(waitForAnimations); } return result; } @Override - public void syncInputTransactions() { + public void syncInputTransactions(boolean waitForAnimations) { final long token = Binder.clearCallingIdentity(); try { - waitForAnimationsToComplete(); + if (waitForAnimations) { + waitForAnimationsToComplete(); + } // Collect all input transactions from all displays to make sure we could sync all input // windows at same time.