diff options
| -rw-r--r-- | services/core/java/com/android/server/input/InputManagerService.java | 102 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/InputManagerCallback.java | 11 | ||||
| -rw-r--r-- | services/core/jni/com_android_server_input_InputManagerService.cpp | 5 | ||||
| -rw-r--r-- | tests/Input/Android.bp | 12 | ||||
| -rw-r--r-- | tests/Input/AndroidManifest.xml | 35 | ||||
| -rw-r--r-- | tests/Input/AndroidTest.xml | 25 | ||||
| -rw-r--r-- | tests/Input/src/com/android/test/input/AnrTest.kt | 115 | ||||
| -rw-r--r-- | tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt | 52 |
8 files changed, 334 insertions, 23 deletions
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 74ed815f080a..5a4237938086 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -17,6 +17,7 @@ package com.android.server.input; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -66,6 +67,7 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -181,8 +183,7 @@ public class InputManagerService extends IInputManager.Stub // State for vibrator tokens. private Object mVibratorLock = new Object(); - private HashMap<IBinder, VibratorToken> mVibratorTokens = - new HashMap<IBinder, VibratorToken>(); + private Map<IBinder, VibratorToken> mVibratorTokens = new ArrayMap<IBinder, VibratorToken>(); private int mNextVibratorTokenValue; // State for the currently installed input filter. @@ -190,12 +191,16 @@ public class InputManagerService extends IInputManager.Stub IInputFilter mInputFilter; // guarded by mInputFilterLock InputFilterHost mInputFilterHost; // guarded by mInputFilterLock + private final Object mGestureMonitorPidsLock = new Object(); + @GuardedBy("mGestureMonitorPidsLock") + private final ArrayMap<IBinder, Integer> mGestureMonitorPidsByToken = new ArrayMap<>(); + // The associations of input devices to displays by port. Maps from input device port (String) // to display id (int). Currently only accessed by InputReader. private final Map<String, Integer> mStaticAssociations; private final Object mAssociationsLock = new Object(); @GuardedBy("mAssociationLock") - private final Map<String, Integer> mRuntimeAssociations = new HashMap<String, Integer>(); + private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<String, Integer>(); private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); @@ -540,13 +545,17 @@ public class InputManagerService extends IInputManager.Stub if (displayId < Display.DEFAULT_DISPLAY) { throw new IllegalArgumentException("displayId must >= 0."); } + final int pid = Binder.getCallingPid(); final long ident = Binder.clearCallingIdentity(); try { InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName); InputMonitorHost host = new InputMonitorHost(inputChannels[0]); - nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, - true /*isGestureMonitor*/); + nativeRegisterInputMonitor( + mPtr, inputChannels[0], displayId, true /*isGestureMonitor*/); + synchronized (mGestureMonitorPidsLock) { + mGestureMonitorPidsByToken.put(inputChannels[1].getToken(), pid); + } return new InputMonitor(inputChannels[1], host); } finally { Binder.restoreCallingIdentity(ident); @@ -575,6 +584,9 @@ public class InputManagerService extends IInputManager.Stub if (inputChannel == null) { throw new IllegalArgumentException("inputChannel must not be null."); } + synchronized (mGestureMonitorPidsLock) { + mGestureMonitorPidsByToken.remove(inputChannel.getToken()); + } nativeUnregisterInputChannel(mPtr, inputChannel); } @@ -1838,6 +1850,7 @@ public class InputManagerService extends IInputManager.Stub if (dumpStr != null) { pw.println(dumpStr); dumpAssociations(pw); + dumpGestureMonitorPidsByToken(pw); } } @@ -1861,6 +1874,19 @@ public class InputManagerService extends IInputManager.Stub } } + private void dumpGestureMonitorPidsByToken(PrintWriter pw) { + synchronized (mGestureMonitorPidsLock) { + if (!mGestureMonitorPidsByToken.isEmpty()) { + pw.println("Gesture monitor pids by token:"); + for (int i = 0; i < mGestureMonitorPidsByToken.size(); i++) { + pw.print(" " + i + ": "); + pw.print(" token: " + mGestureMonitorPidsByToken.keyAt(i)); + pw.println(" pid: " + mGestureMonitorPidsByToken.valueAt(i)); + } + } + } + } + private boolean checkCallingPermission(String permission, String func) { // Quick check: if the calling permission is me, it's all okay. if (Binder.getCallingPid() == Process.myPid()) { @@ -1883,6 +1909,7 @@ public class InputManagerService extends IInputManager.Stub public void monitor() { synchronized (mInputFilterLock) { } synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */} + synchronized (mGestureMonitorPidsLock) { /* Test if blocked by gesture monitor pids lock */} nativeMonitor(mPtr); } @@ -1944,6 +1971,9 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private void notifyInputChannelBroken(IBinder token) { + synchronized (mGestureMonitorPidsLock) { + mGestureMonitorPidsByToken.remove(token); + } mWindowManagerCallbacks.notifyInputChannelBroken(token); } @@ -1959,8 +1989,12 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private long notifyANR(InputApplicationHandle inputApplicationHandle, IBinder token, String reason) { - return mWindowManagerCallbacks.notifyANR(inputApplicationHandle, - token, reason); + Integer gestureMonitorPid; + synchronized (mGestureMonitorPidsLock) { + gestureMonitorPid = mGestureMonitorPidsByToken.get(token); + } + return mWindowManagerCallbacks.notifyANR(inputApplicationHandle, token, gestureMonitorPid, + reason); } // Native callback. @@ -2206,22 +2240,48 @@ public class InputManagerService extends IInputManager.Stub * Callback interface implemented by the Window Manager. */ public interface WindowManagerCallbacks { + /** + * This callback is invoked when the confuguration changes. + */ public void notifyConfigurationChanged(); + /** + * This callback is invoked when the lid switch changes state. + * @param whenNanos the time when the change occurred + * @param lidOpen true if the lid is open + */ public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen); + /** + * This callback is invoked when the camera lens cover switch changes state. + * @param whenNanos the time when the change occurred + * @param lensCovered true is the lens is covered + */ public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered); + /** + * This callback is invoked when an input channel is closed unexpectedly. + * @param token the connection token of the broken channel + */ public void notifyInputChannelBroken(IBinder token); /** - * Notifies the window manager about an application that is not responding. - * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch. + * Notify the window manager about an application that is not responding. + * Return a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch. */ long notifyANR(InputApplicationHandle inputApplicationHandle, IBinder token, - String reason); + @Nullable Integer pid, String reason); - public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags); + /** + * This callback is invoked when an event first arrives to InputDispatcher and before it is + * placed onto InputDispatcher's queue. If this event is intercepted, it will never be + * processed by InputDispacher. + * @param event The key event that's arriving to InputDispatcher + * @param policyFlags The policy flags + * @return the flags that tell InputDispatcher how to handle the event (for example, whether + * to pass it to the user) + */ + int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags); /** * Provides an opportunity for the window manager policy to intercept early motion event @@ -2231,11 +2291,23 @@ public class InputManagerService extends IInputManager.Stub int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos, int policyFlags); - public long interceptKeyBeforeDispatching(IBinder token, - KeyEvent event, int policyFlags); + /** + * This callback is invoked just before the key is about to be sent to an application. + * This allows the policy to make some last minute decisions on whether to intercept this + * key. + * @param token the window token that's about to receive this event + * @param event the key event that's being dispatched + * @param policyFlags the policy flags + * @return negative value if the key should be skipped (not sent to the app). 0 if the key + * should proceed getting dispatched to the app. positive value to indicate the additional + * time delay, in nanoseconds, to wait before sending this key to the app. + */ + long interceptKeyBeforeDispatching(IBinder token, KeyEvent event, int policyFlags); - public KeyEvent dispatchUnhandledKey(IBinder token, - KeyEvent event, int policyFlags); + /** + * Dispatch unhandled key + */ + KeyEvent dispatchUnhandledKey(IBinder token, KeyEvent event, int policyFlags); public int getPointerLayer(); diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 5e1cbc3158c1..e166bfc08ad4 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -10,6 +10,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS; +import android.annotation.Nullable; import android.os.Build; import android.os.Debug; import android.os.IBinder; @@ -173,23 +174,23 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal */ @Override public long notifyANR(InputApplicationHandle inputApplicationHandle, IBinder token, - String reason) { + @Nullable Integer pid, String reason) { final long startTime = SystemClock.uptimeMillis(); try { - return notifyANRInner(inputApplicationHandle, token, reason); + return notifyANRInner(inputApplicationHandle, token, pid, reason); } finally { // Log the time because the method is called from InputDispatcher thread. It shouldn't - // take too long that may affect input response time. + // take too long because it blocks input while executing. Slog.d(TAG_WM, "notifyANR took " + (SystemClock.uptimeMillis() - startTime) + "ms"); } } private long notifyANRInner(InputApplicationHandle inputApplicationHandle, IBinder token, - String reason) { + @Nullable Integer pid, String reason) { ActivityRecord activity = null; WindowState windowState = null; boolean aboveSystem = false; - int windowPid = INVALID_PID; + int windowPid = pid != null ? pid : INVALID_PID; preDumpIfLockTooSlow(); diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 784366318319..0202c88009fd 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -710,9 +710,8 @@ std::chrono::nanoseconds NativeInputManager::notifyAnr( jobject tokenObj = javaObjectForIBinder(env, token); jstring reasonObj = env->NewStringUTF(reason.c_str()); - jlong newTimeout = env->CallLongMethod(mServiceObj, - gServiceClassInfo.notifyANR, inputApplicationHandleObj, tokenObj, - reasonObj); + jlong newTimeout = env->CallLongMethod(mServiceObj, gServiceClassInfo.notifyANR, + inputApplicationHandleObj, tokenObj, reasonObj); if (checkAndClearExceptionFromCallback(env, "notifyANR")) { newTimeout = 0; // abort dispatch } else { diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp new file mode 100644 index 000000000000..9d35cbc3de7f --- /dev/null +++ b/tests/Input/Android.bp @@ -0,0 +1,12 @@ +android_test { + name: "InputTests", + srcs: ["src/**/*.kt"], + platform_apis: true, + certificate: "platform", + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.rules", + "android-support-test", + "ub-uiautomator", + ], +} diff --git a/tests/Input/AndroidManifest.xml b/tests/Input/AndroidManifest.xml new file mode 100644 index 000000000000..4195df72864c --- /dev/null +++ b/tests/Input/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.input"> + <uses-permission android:name="android.permission.MONITOR_INPUT"/> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/> + <uses-permission android:name="android.permission.INJECT_EVENTS"/> + + <application android:label="InputTest"> + + <activity android:name=".UnresponsiveGestureMonitorActivity" + android:label="Unresponsive gesture monitor" + android:process=":externalProcess"> + </activity> + + + </application> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.test.input" + android:label="Input Tests"/> +</manifest> diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml new file mode 100644 index 000000000000..c62db1ea5ca9 --- /dev/null +++ b/tests/Input/AndroidTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright 2020 Google Inc. All Rights Reserved. + --> +<configuration description="Runs Input Tests"> + <option name="test-tag" value="InputTests" /> + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <!-- keeps the screen on during tests --> + <option name="screen-always-on" value="on" /> + <!-- prevents the phone from restarting --> + <option name="force-skip-system-props" value="true" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true"/> + <option name="test-file-name" value="InputTests.apk"/> + + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.test.input"/> + <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" /> + <option name="shell-timeout" value="660s" /> + <option name="test-timeout" value="600s" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt new file mode 100644 index 000000000000..4da3eca25ea0 --- /dev/null +++ b/tests/Input/src/com/android/test/input/AnrTest.kt @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.test.input + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.filters.MediumTest + +import android.graphics.Rect +import android.os.SystemClock +import android.provider.Settings +import android.provider.Settings.Global.HIDE_ERROR_DIALOGS +import android.support.test.uiautomator.By +import android.support.test.uiautomator.UiDevice +import android.support.test.uiautomator.UiObject2 +import android.support.test.uiautomator.Until +import android.view.InputDevice +import android.view.MotionEvent + +import org.junit.After +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** + * This test makes sure that an unresponsive gesture monitor gets an ANR. + * + * The gesture monitor must be registered from a different process than the instrumented process. + * Otherwise, when the test runs, you will get: + * Test failed to run to completion. + * Reason: 'Instrumentation run failed due to 'keyDispatchingTimedOut''. + * Check device logcat for details + * RUNNER ERROR: Instrumentation run failed due to 'keyDispatchingTimedOut' + */ +@MediumTest +@RunWith(AndroidJUnit4::class) +class AnrTest { + companion object { + private const val TAG = "AnrTest" + } + + val mInstrumentation = InstrumentationRegistry.getInstrumentation() + var mHideErrorDialogs = 0 + + @Before + fun setUp() { + val contentResolver = mInstrumentation.targetContext.contentResolver + mHideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0) + Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0) + } + + @After + fun tearDown() { + val contentResolver = mInstrumentation.targetContext.contentResolver + Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, mHideErrorDialogs) + } + + @Test + fun testGestureMonitorAnr() { + startUnresponsiveActivity() + val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) + val obj: UiObject2? = uiDevice.wait(Until.findObject( + By.text("Unresponsive gesture monitor")), 10000) + + if (obj == null) { + fail("Could not find unresponsive activity") + return + } + + val rect: Rect = obj.visibleBounds + val downTime = SystemClock.uptimeMillis() + val downEvent = MotionEvent.obtain(downTime, downTime, + MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */) + downEvent.source = InputDevice.SOURCE_TOUCHSCREEN + + mInstrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/) + + // Todo: replace using timeout from android.hardware.input.IInputManager + SystemClock.sleep(5000) // default ANR timeout for gesture monitors + + clickCloseAppOnAnrDialog() + } + + private fun clickCloseAppOnAnrDialog() { + // Find anr dialog and kill app + val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) + val closeAppButton: UiObject2? = + uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000) + if (closeAppButton == null) { + fail("Could not find anr dialog") + return + } + closeAppButton.click() + } + + private fun startUnresponsiveActivity() { + val flags = " -W -n " + val startCmd = "am start $flags com.android.test.input/.UnresponsiveGestureMonitorActivity" + mInstrumentation.uiAutomation.executeShellCommand(startCmd) + } +}
\ No newline at end of file diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt new file mode 100644 index 000000000000..d83a4570fedc --- /dev/null +++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.input + +import android.app.Activity +import android.hardware.input.InputManager +import android.os.Bundle +import android.os.Looper +import android.util.Log +import android.view.InputChannel +import android.view.InputEvent +import android.view.InputEventReceiver +import android.view.InputMonitor + +class UnresponsiveReceiver(channel: InputChannel, looper: Looper) : + InputEventReceiver(channel, looper) { + companion object { + const val TAG = "UnresponsiveReceiver" + } + override fun onInputEvent(event: InputEvent) { + Log.i(TAG, "Received $event") + // Not calling 'finishInputEvent' in order to trigger the ANR + } +} + +class UnresponsiveGestureMonitorActivity : Activity() { + companion object { + const val MONITOR_NAME = "unresponsive gesture monitor" + } + private lateinit var mInputEventReceiver: InputEventReceiver + private lateinit var mInputMonitor: InputMonitor + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mInputMonitor = InputManager.getInstance().monitorGestureInput(MONITOR_NAME, displayId) + mInputEventReceiver = UnresponsiveReceiver( + mInputMonitor.getInputChannel(), Looper.myLooper()) + } +} |