diff options
author | Jeff Brown <jeffbrown@google.com> | 2010-06-22 01:27:15 -0700 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2010-06-28 19:10:54 -0700 |
commit | 349703effce5acc53ed96f7ed8556131f0c65e18 (patch) | |
tree | 359217d5076e3005c724b2117a59ffec81e7a83b | |
parent | f2b544f5ae7676f7ab4cdf3379b2ed3c60a65def (diff) | |
download | frameworks_base-349703effce5acc53ed96f7ed8556131f0c65e18.zip frameworks_base-349703effce5acc53ed96f7ed8556131f0c65e18.tar.gz frameworks_base-349703effce5acc53ed96f7ed8556131f0c65e18.tar.bz2 |
Native input event dispatching.
Target identification is now fully native.
Fixed a couple of minor issues related to input injection.
Native input enabled by default, can be disabled by setting
WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH to false.
Change-Id: I7edf66ed3e987cc9306ad4743ac57a116af452ff
23 files changed, 2135 insertions, 910 deletions
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 3f5d6ca..249ad62 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -773,8 +773,6 @@ public abstract class WallpaperService extends Service { if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { if (mInputChannel != null) { InputQueue.unregisterInputChannel(mInputChannel); - mInputChannel.dispose(); - mInputChannel = null; } } @@ -783,6 +781,15 @@ public abstract class WallpaperService extends Service { } mSurfaceHolder.mSurface.release(); mCreated = false; + + if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { + // Dispose the input channel after removing the window so the Window Manager + // doesn't interpret the input channel being closed as an abnormal termination. + if (mInputChannel != null) { + mInputChannel.dispose(); + mInputChannel = null; + } + } } } } diff --git a/core/java/android/view/InputTarget.java b/core/java/android/view/InputTarget.java deleted file mode 100644 index 6ff7305..0000000 --- a/core/java/android/view/InputTarget.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2010 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 android.view; - -/** - * An input target specifies how an input event is to be dispatched to a particular window - * including the window's input channel, control flags, a timeout, and an X / Y offset to - * be added to input event coordinates to compensate for the absolute position of the - * window area. - * - * These parameters are used by the native input dispatching code. - * @hide - */ -public final class InputTarget { - public InputChannel mInputChannel; - public int mFlags; - public long mTimeoutNanos; - public float mXOffset; - public float mYOffset; - - /** - * This flag indicates that subsequent event delivery should be held until the - * current event is delivered to this target or a timeout occurs. - */ - public static int FLAG_SYNC = 0x01; - - /** - * This flag indicates that a MotionEvent with ACTION_DOWN falls outside of the area of - * this target and so should instead be delivered as an ACTION_OUTSIDE to this target. - */ - public static int FLAG_OUTSIDE = 0x02; - - /* - * This flag indicates that a KeyEvent or MotionEvent is being canceled. - * In the case of a key event, it should be delivered with KeyEvent.FLAG_CANCELED set. - * In the case of a motion event, it should be delivered as MotionEvent.ACTION_CANCEL. - */ - public static int FLAG_CANCEL = 0x04; - - public void recycle() { - mInputChannel = null; - } -} diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 4854190..1dc82e8 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -1762,6 +1762,15 @@ public final class ViewRoot extends Handler implements ViewParent, sWindowSession.remove(mWindow); } catch (RemoteException e) { } + + if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { + // Dispose the input channel after removing the window so the Window Manager + // doesn't interpret the input channel being closed as an abnormal termination. + if (mInputChannel != null) { + mInputChannel.dispose(); + mInputChannel = null; + } + } } void updateConfiguration(Configuration config, boolean force) { diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 431b786..be1f6d2 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -83,7 +83,7 @@ public interface WindowManagerPolicy { * Temporary flag added during the transition to the new native input dispatcher. * This will be removed when the old input dispatch code is deleted. */ - public final static boolean ENABLE_NATIVE_INPUT_DISPATCH = false; + public final static boolean ENABLE_NATIVE_INPUT_DISPATCH = true; // flags for interceptKeyTq /** diff --git a/core/jni/Android.mk b/core/jni/Android.mk index d854e87..a008e96 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -47,7 +47,6 @@ LOCAL_SRC_FILES:= \ android_view_ViewRoot.cpp \ android_view_InputChannel.cpp \ android_view_InputQueue.cpp \ - android_view_InputTarget.cpp \ android_view_KeyEvent.cpp \ android_view_MotionEvent.cpp \ android_text_AndroidCharacter.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 3e69c78..6fb1369 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -164,7 +164,6 @@ extern int register_android_backup_BackupHelperDispatcher(JNIEnv *env); extern int register_android_app_NativeActivity(JNIEnv *env); extern int register_android_view_InputChannel(JNIEnv* env); extern int register_android_view_InputQueue(JNIEnv* env); -extern int register_android_view_InputTarget(JNIEnv* env); extern int register_android_view_KeyEvent(JNIEnv* env); extern int register_android_view_MotionEvent(JNIEnv* env); @@ -1297,7 +1296,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_app_NativeActivity), REG_JNI(register_android_view_InputChannel), REG_JNI(register_android_view_InputQueue), - REG_JNI(register_android_view_InputTarget), REG_JNI(register_android_view_KeyEvent), REG_JNI(register_android_view_MotionEvent), }; diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp index 63e00d7..6fb3cf7 100644 --- a/core/jni/android_view_InputQueue.cpp +++ b/core/jni/android_view_InputQueue.cpp @@ -19,10 +19,10 @@ //#define LOG_NDEBUG 0 // Log debug messages about the dispatch cycle. -#define DEBUG_DISPATCH_CYCLE 1 +#define DEBUG_DISPATCH_CYCLE 0 // Log debug messages about registrations. -#define DEBUG_REGISTRATION 1 +#define DEBUG_REGISTRATION 0 #include "JNIHelp.h" diff --git a/core/jni/android_view_InputTarget.cpp b/core/jni/android_view_InputTarget.cpp deleted file mode 100644 index a0a7054..0000000 --- a/core/jni/android_view_InputTarget.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2010 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. - */ - -#define LOG_TAG "InputTarget-JNI" - -#include "JNIHelp.h" - -#include <utils/Log.h> -#include <ui/InputDispatcher.h> -#include <ui/InputTransport.h> -#include "android_view_InputTarget.h" -#include "android_view_InputChannel.h" - -namespace android { - -// ---------------------------------------------------------------------------- - -static struct { - jclass clazz; - - jfieldID mInputChannel; - jfieldID mFlags; - jfieldID mTimeoutNanos; - jfieldID mXOffset; - jfieldID mYOffset; -} gInputTargetClassInfo; - -// ---------------------------------------------------------------------------- - -void android_view_InputTarget_toNative(JNIEnv* env, jobject inputTargetObj, - InputTarget* outInputTarget) { - jobject inputChannelObj = env->GetObjectField(inputTargetObj, - gInputTargetClassInfo.mInputChannel); - jint flags = env->GetIntField(inputTargetObj, - gInputTargetClassInfo.mFlags); - jlong timeoutNanos = env->GetLongField(inputTargetObj, - gInputTargetClassInfo.mTimeoutNanos); - jfloat xOffset = env->GetFloatField(inputTargetObj, - gInputTargetClassInfo.mXOffset); - jfloat yOffset = env->GetFloatField(inputTargetObj, - gInputTargetClassInfo.mYOffset); - - outInputTarget->inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); - outInputTarget->flags = flags; - outInputTarget->timeout = timeoutNanos; - outInputTarget->xOffset = xOffset; - outInputTarget->yOffset = yOffset; - - env->DeleteLocalRef(inputChannelObj); -} - -// ---------------------------------------------------------------------------- - -#define FIND_CLASS(var, className) \ - var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); - -#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ - var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ - LOG_FATAL_IF(! var, "Unable to find field " fieldName); - -int register_android_view_InputTarget(JNIEnv* env) { - FIND_CLASS(gInputTargetClassInfo.clazz, "android/view/InputTarget"); - - GET_FIELD_ID(gInputTargetClassInfo.mInputChannel, gInputTargetClassInfo.clazz, - "mInputChannel", "Landroid/view/InputChannel;"); - - GET_FIELD_ID(gInputTargetClassInfo.mFlags, gInputTargetClassInfo.clazz, - "mFlags", "I"); - - GET_FIELD_ID(gInputTargetClassInfo.mTimeoutNanos, gInputTargetClassInfo.clazz, - "mTimeoutNanos", "J"); - - GET_FIELD_ID(gInputTargetClassInfo.mXOffset, gInputTargetClassInfo.clazz, - "mXOffset", "F"); - - GET_FIELD_ID(gInputTargetClassInfo.mYOffset, gInputTargetClassInfo.clazz, - "mYOffset", "F"); - - return 0; -} - -} // namespace android diff --git a/include/ui/Input.h b/include/ui/Input.h index 32f85b3..57b292b 100644 --- a/include/ui/Input.h +++ b/include/ui/Input.h @@ -87,6 +87,9 @@ enum { // Indicates that the screen was dim when the event was received and the event // should brighten the device. POLICY_FLAG_BRIGHT_HERE = 0x20000000, + + // Indicates that the dispatcher should call back into the policy before dispatching. */ + POLICY_FLAG_INTERCEPT_DISPATCH = 0x40000000, }; /* diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h index 511ad20..eb8f820 100644 --- a/include/ui/InputDispatcher.h +++ b/include/ui/InputDispatcher.h @@ -126,21 +126,21 @@ public: /* Gets the key repeat timeout or -1 if automatic key repeating is disabled. */ virtual nsecs_t getKeyRepeatTimeout() = 0; - /* Gets the input targets for a key event. + /* Waits for key event input targets to become available. * If the event is being injected, injectorPid and injectorUid should specify the * process id and used id of the injecting application, otherwise they should both * be -1. * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */ - virtual int32_t getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, + virtual int32_t waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) = 0; - /* Gets the input targets for a motion event. + /* Waits for motion event targets to become available. * If the event is being injected, injectorPid and injectorUid should specify the * process id and used id of the injecting application, otherwise they should both * be -1. * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */ - virtual int32_t getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, + virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) = 0; }; @@ -186,6 +186,16 @@ public: virtual int32_t injectInputEvent(const InputEvent* event, int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) = 0; + /* Preempts input dispatch in progress by making pending synchronous + * dispatches asynchronous instead. This method is generally called during a focus + * transition from one application to the next so as to enable the new application + * to start receiving input as soon as possible without having to wait for the + * old application to finish up. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void preemptInputDispatch() = 0; + /* Registers or unregister input channels that may be used as targets for input events. * * These methods may be called on any thread (usually by the input manager). @@ -233,6 +243,8 @@ public: virtual int32_t injectInputEvent(const InputEvent* event, int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis); + virtual void preemptInputDispatch(); + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel); virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel); diff --git a/include/ui/InputManager.h b/include/ui/InputManager.h index 7509dd2..e755238 100644 --- a/include/ui/InputManager.h +++ b/include/ui/InputManager.h @@ -87,6 +87,14 @@ public: virtual int32_t injectInputEvent(const InputEvent* event, int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) = 0; + /* Preempts input dispatch in progress by making pending synchronous + * dispatches asynchronous instead. This method is generally called during a focus + * transition from one application to the next so as to enable the new application + * to start receiving input as soon as possible without having to wait for the + * old application to finish up. + */ + virtual void preemptInputDispatch() = 0; + /* Gets input device configuration. */ virtual void getInputConfiguration(InputConfiguration* outConfiguration) const = 0; @@ -130,6 +138,8 @@ public: virtual int32_t injectInputEvent(const InputEvent* event, int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis); + virtual void preemptInputDispatch(); + virtual void getInputConfiguration(InputConfiguration* outConfiguration) const; virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses, int32_t scanCode) const; diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h index d76b8fe..2093560 100644 --- a/include/ui/InputReader.h +++ b/include/ui/InputReader.h @@ -361,7 +361,11 @@ public: // The input dispatcher should add POLICY_FLAG_BRIGHT_HERE to the policy flags it // passes through the dispatch pipeline. - ACTION_BRIGHT_HERE = 0x00000008 + ACTION_BRIGHT_HERE = 0x00000008, + + // The input dispatcher should add POLICY_FLAG_INTERCEPT_DISPATCH to the policy flags + // it passed through the dispatch pipeline. + ACTION_INTERCEPT_DISPATCH = 0x00000010 }; /* Describes a virtual key. */ diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp index b3103a4..0fc29b2 100644 --- a/libs/ui/InputDispatcher.cpp +++ b/libs/ui/InputDispatcher.cpp @@ -8,25 +8,25 @@ //#define LOG_NDEBUG 0 // Log detailed debug messages about each inbound event notification to the dispatcher. -#define DEBUG_INBOUND_EVENT_DETAILS 1 +#define DEBUG_INBOUND_EVENT_DETAILS 0 // Log detailed debug messages about each outbound event processed by the dispatcher. -#define DEBUG_OUTBOUND_EVENT_DETAILS 1 +#define DEBUG_OUTBOUND_EVENT_DETAILS 0 // Log debug messages about batching. -#define DEBUG_BATCHING 1 +#define DEBUG_BATCHING 0 // Log debug messages about the dispatch cycle. -#define DEBUG_DISPATCH_CYCLE 1 +#define DEBUG_DISPATCH_CYCLE 0 // Log debug messages about registrations. -#define DEBUG_REGISTRATION 1 +#define DEBUG_REGISTRATION 0 // Log debug messages about performance statistics. -#define DEBUG_PERFORMANCE_STATISTICS 1 +#define DEBUG_PERFORMANCE_STATISTICS 0 // Log debug messages about input event injection. -#define DEBUG_INJECTION 1 +#define DEBUG_INJECTION 0 #include <cutils/log.h> #include <ui/InputDispatcher.h> @@ -249,9 +249,7 @@ void InputDispatcher::processKeyLockedInterruptible( entry->downTime); #endif - // TODO: Poke user activity. - - if (entry->action == KEY_EVENT_ACTION_DOWN) { + if (entry->action == KEY_EVENT_ACTION_DOWN && ! entry->isInjected()) { if (mKeyRepeatState.lastKeyEntry && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) { // We have seen two identical key downs in a row which indicates that the device @@ -277,14 +275,24 @@ void InputDispatcher::processKeyLockedInterruptible( void InputDispatcher::processKeyRepeatLockedInterruptible( nsecs_t currentTime, nsecs_t keyRepeatTimeout) { - // TODO Old WindowManagerServer code sniffs the input queue for following key up - // events and drops the repeat if one is found. We should do something similar. - // One good place to do it is in notifyKey as soon as the key up enters the - // inbound event queue. + KeyEntry* entry = mKeyRepeatState.lastKeyEntry; + + // Search the inbound queue for a key up corresponding to this device. + // It doesn't make sense to generate a key repeat event if the key is already up. + for (EventEntry* queuedEntry = mInboundQueue.head.next; + queuedEntry != & mInboundQueue.tail; queuedEntry = entry->next) { + if (queuedEntry->type == EventEntry::TYPE_KEY) { + KeyEntry* queuedKeyEntry = static_cast<KeyEntry*>(queuedEntry); + if (queuedKeyEntry->deviceId == entry->deviceId + && entry->action == KEY_EVENT_ACTION_UP) { + resetKeyRepeatLocked(); + return; + } + } + } // Synthesize a key repeat after the repeat timeout expired. - // We reuse the previous key entry if otherwise unreferenced. - KeyEntry* entry = mKeyRepeatState.lastKeyEntry; + // Reuse the repeated key entry if it is otherwise unreferenced. uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK; if (entry->refCount == 1) { entry->eventTime = currentTime; @@ -366,7 +374,7 @@ void InputDispatcher::identifyInputTargetsAndDispatchKeyLockedInterruptible( entry->downTime, entry->eventTime); mCurrentInputTargets.clear(); - int32_t injectionResult = mPolicy->getKeyEventTargets(& mReusableKeyEvent, + int32_t injectionResult = mPolicy->waitForKeyEventTargets(& mReusableKeyEvent, entry->policyFlags, entry->injectorPid, entry->injectorUid, mCurrentInputTargets); @@ -375,7 +383,9 @@ void InputDispatcher::identifyInputTargetsAndDispatchKeyLockedInterruptible( setInjectionResultLocked(entry, injectionResult); - dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) { + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + } } void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible( @@ -395,7 +405,7 @@ void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible( entry->firstSample.pointerCoords); mCurrentInputTargets.clear(); - int32_t injectionResult = mPolicy->getMotionEventTargets(& mReusableMotionEvent, + int32_t injectionResult = mPolicy->waitForMotionEventTargets(& mReusableMotionEvent, entry->policyFlags, entry->injectorPid, entry->injectorUid, mCurrentInputTargets); @@ -404,7 +414,9 @@ void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible( setInjectionResultLocked(entry, injectionResult); - dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) { + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + } } void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, @@ -514,7 +526,7 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, connection->getInputChannelName()); } else if (status == status_t(FAILED_TRANSACTION)) { LOGD("channel '%s' ~ Could not append motion sample to currently " - "dispatchedmove event because the event has already been consumed. " + "dispatched move event because the event has already been consumed. " "(Waiting for next dispatch cycle to start.)", connection->getInputChannelName()); } else { @@ -1253,9 +1265,37 @@ void InputDispatcher::resetKeyRepeatLocked() { } } +void InputDispatcher::preemptInputDispatch() { +#if DEBUG_DISPATCH_CYCLE + LOGD("preemptInputDispatch"); +#endif + + bool preemptedOne = false; + { // acquire lock + AutoMutex _l(mLock); + + for (size_t i = 0; i < mActiveConnections.size(); i++) { + Connection* connection = mActiveConnections[i]; + if (connection->hasPendingSyncTarget()) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Preempted pending synchronous dispatch", + connection->getInputChannelName()); +#endif + connection->outboundQueue.tail.prev->targetFlags &= ~ InputTarget::FLAG_SYNC; + preemptedOne = true; + } + } + } // release lock + + if (preemptedOne) { + // Wake up the poll loop so it can get a head start dispatching the next event. + mPollLoop->wake(); + } +} + status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) { #if DEBUG_REGISTRATION - LOGD("channel '%s' - Registered", inputChannel->getName().string()); + LOGD("channel '%s' ~ registerInputChannel", inputChannel->getName().string()); #endif int receiveFd; @@ -1288,7 +1328,7 @@ status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChan status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) { #if DEBUG_REGISTRATION - LOGD("channel '%s' - Unregistered", inputChannel->getName().string()); + LOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string()); #endif int32_t receiveFd; diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp index 32c58b4..e1d15a4 100644 --- a/libs/ui/InputManager.cpp +++ b/libs/ui/InputManager.cpp @@ -85,6 +85,10 @@ int32_t InputManager::injectInputEvent(const InputEvent* event, return mDispatcher->injectInputEvent(event, injectorPid, injectorUid, sync, timeoutMillis); } +void InputManager::preemptInputDispatch() { + mDispatcher->preemptInputDispatch(); +} + void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) const { mReader->getCurrentInputConfiguration(outConfiguration); } diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp index 1824054..8087f84 100644 --- a/libs/ui/InputReader.cpp +++ b/libs/ui/InputReader.cpp @@ -11,13 +11,13 @@ #define DEBUG_RAW_EVENTS 0 // Log debug messages about touch screen filtering hacks. -#define DEBUG_HACKS 1 +#define DEBUG_HACKS 0 // Log debug messages about virtual key processing. -#define DEBUG_VIRTUAL_KEYS 1 +#define DEBUG_VIRTUAL_KEYS 0 // Log debug messages about pointers. -#define DEBUG_POINTERS 1 +#define DEBUG_POINTERS 0 // Log debug messages about pointer assignment calculations. #define DEBUG_POINTER_ASSIGNMENT 0 @@ -630,7 +630,8 @@ void InputDevice::TouchScreenState::applyAveragingTouchFilter() { int32_t pressure = currentTouch.pointers[currentIndex].pressure; if (lastTouch.idBits.hasBit(id)) { - // Pointer still down compute average. + // Pointer was down before and is still down now. + // Compute average over history trace. uint32_t start = averagingTouchFilter.historyStart[id]; uint32_t end = averagingTouchFilter.historyEnd[id]; @@ -644,11 +645,15 @@ void InputDevice::TouchScreenState::applyAveragingTouchFilter() { #endif if (distance < AVERAGING_DISTANCE_LIMIT) { + // Increment end index in preparation for recording new historical data. end += 1; if (end > AVERAGING_HISTORY_SIZE) { end = 0; } + // If the end index has looped back to the start index then we have filled + // the historical trace up to the desired size so we drop the historical + // data at the start of the trace. if (end == start) { start += 1; if (start > AVERAGING_HISTORY_SIZE) { @@ -656,23 +661,25 @@ void InputDevice::TouchScreenState::applyAveragingTouchFilter() { } } + // Add the raw data to the historical trace. averagingTouchFilter.historyStart[id] = start; averagingTouchFilter.historyEnd[id] = end; averagingTouchFilter.historyData[end].pointers[id].x = x; averagingTouchFilter.historyData[end].pointers[id].y = y; averagingTouchFilter.historyData[end].pointers[id].pressure = pressure; + // Average over all historical positions in the trace by total pressure. int32_t averagedX = 0; int32_t averagedY = 0; int32_t totalPressure = 0; for (;;) { int32_t historicalX = averagingTouchFilter.historyData[start].pointers[id].x; - int32_t historicalY = averagingTouchFilter.historyData[start].pointers[id].x; + int32_t historicalY = averagingTouchFilter.historyData[start].pointers[id].y; int32_t historicalPressure = averagingTouchFilter.historyData[start] .pointers[id].pressure; - averagedX += historicalX; - averagedY += historicalY; + averagedX += historicalX * historicalPressure; + averagedY += historicalY * historicalPressure; totalPressure += historicalPressure; if (start == end) { @@ -1144,12 +1151,6 @@ void InputReader::onMultiTouchScreenStateChanged(nsecs_t when, void InputReader::onSingleTouchScreenStateChanged(nsecs_t when, InputDevice* device) { - static const uint32_t POSITION_FIELDS = - InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X - | InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y - | InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE - | InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH; - /* Refresh display properties so we can map touch screen coords into display coords */ if (! refreshDisplayProperties()) { @@ -1167,10 +1168,19 @@ void InputReader::onSingleTouchScreenStateChanged(nsecs_t when, in->current.down = in->accumulator.btnTouch; } - if ((fields & POSITION_FIELDS) == POSITION_FIELDS) { + if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X) { in->current.x = in->accumulator.absX; + } + + if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y) { in->current.y = in->accumulator.absY; + } + + if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE) { in->current.pressure = in->accumulator.absPressure; + } + + if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH) { in->current.size = in->accumulator.absToolWidth; } @@ -1323,18 +1333,23 @@ bool InputReader::consumeVirtualKeyTouches(nsecs_t when, void InputReader::dispatchVirtualKey(nsecs_t when, InputDevice* device, uint32_t policyFlags, int32_t keyEventAction, int32_t keyEventFlags) { + updateExportedVirtualKeyState(); + int32_t keyCode = device->touchScreen.currentVirtualKey.keyCode; int32_t scanCode = device->touchScreen.currentVirtualKey.scanCode; nsecs_t downTime = device->touchScreen.currentVirtualKey.downTime; int32_t metaState = globalMetaState(); - updateExportedVirtualKeyState(); - mPolicy->virtualKeyFeedback(when, device->id, keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime); - mDispatcher->notifyKey(when, device->id, INPUT_EVENT_NATURE_KEY, policyFlags, - keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime); + int32_t policyActions = mPolicy->interceptKey(when, device->id, + keyEventAction == KEY_EVENT_ACTION_DOWN, keyCode, scanCode, policyFlags); + + if (applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { + mDispatcher->notifyKey(when, device->id, INPUT_EVENT_NATURE_KEY, policyFlags, + keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime); + } } void InputReader::dispatchTouches(nsecs_t when, @@ -1609,6 +1624,10 @@ bool InputReader::applyStandardInputDispatchPolicyActions(nsecs_t when, *policyFlags |= POLICY_FLAG_BRIGHT_HERE; } + if (policyActions & InputReaderPolicyInterface::ACTION_INTERCEPT_DISPATCH) { + *policyFlags |= POLICY_FLAG_INTERCEPT_DISPATCH; + } + return policyActions & InputReaderPolicyInterface::ACTION_DISPATCH; } diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp index b2842d0..f56537a 100644 --- a/libs/ui/InputTransport.cpp +++ b/libs/ui/InputTransport.cpp @@ -430,10 +430,12 @@ status_t InputPublisher::appendMotionSample( reinterpret_cast<char*>(mSharedMessage); if (newBytesUsed > mAshmemSize) { +#if DEBUG_TRANSPORT_ACTIONS LOGD("channel '%s' publisher ~ Cannot append motion sample because the shared memory " "buffer is full. Buffer size: %d bytes, pointers: %d, samples: %d", mChannel->getName().string(), mAshmemSize, mMotionEventPointerCount, mSharedMessage->motion.sampleCount); +#endif return NO_MEMORY; } @@ -444,8 +446,10 @@ status_t InputPublisher::appendMotionSample( if (errno == EAGAIN) { // Only possible source of contention is the consumer having consumed (or being in the // process of consuming) the message and left the semaphore count at 0. +#if DEBUG_TRANSPORT_ACTIONS LOGD("channel '%s' publisher ~ Cannot append motion sample because the message has " "already been consumed.", mChannel->getName().string()); +#endif return FAILED_TRANSACTION; } else { LOGE("channel '%s' publisher ~ Error %d in sem_trywait.", diff --git a/core/jni/android_view_InputTarget.h b/services/java/com/android/server/InputApplication.java index 9230b1b..38420d4 100644 --- a/core/jni/android_view_InputTarget.h +++ b/services/java/com/android/server/InputApplication.java @@ -14,18 +14,20 @@ * limitations under the License. */ -#ifndef _ANDROID_VIEW_INPUTTARGET_H -#define _ANDROID_VIEW_INPUTTARGET_H +package com.android.server; -#include "jni.h" - -namespace android { - -class InputTarget; - -extern void android_view_InputTarget_toNative(JNIEnv* env, jobject inputTargetObj, - InputTarget* outInputTarget); - -} // namespace android - -#endif // _ANDROID_OS_INPUTTARGET_H +/** + * Describes input-related application properties for use by the input dispatcher. + * + * @hide + */ +public final class InputApplication { + // Application name. + public String name; + + // Dispatching timeout. + public long dispatchingTimeoutNanos; + + // The application window token. + public Object token; +} diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java index 8d9bb29..2ba2914 100644 --- a/services/java/com/android/server/InputManager.java +++ b/services/java/com/android/server/InputManager.java @@ -17,23 +17,20 @@ package com.android.server; import com.android.internal.util.XmlUtils; -import com.android.server.KeyInputQueue.VirtualKey; import org.xmlpull.v1.XmlPullParser; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.Environment; import android.os.LocalPowerManager; import android.os.PowerManager; -import android.util.Log; import android.util.Slog; import android.util.Xml; import android.view.InputChannel; -import android.view.InputTarget; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.RawInputEvent; import android.view.Surface; import android.view.WindowManagerPolicy; @@ -85,6 +82,10 @@ public class InputManager { int injectorPid, int injectorUid, boolean sync, int timeoutMillis); private static native int nativeInjectMotionEvent(MotionEvent event, int nature, int injectorPid, int injectorUid, boolean sync, int timeoutMillis); + private static native void nativeSetInputWindows(InputWindow[] windows); + private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen); + private static native void nativeSetFocusedApplication(InputApplication application); + private static native void nativePreemptInputDispatch(); // Device class as defined by EventHub. private static final int CLASS_KEYBOARD = 0x00000001; @@ -284,6 +285,22 @@ public class InputManager { sync, timeoutMillis); } + public void setInputWindows(InputWindow[] windows) { + nativeSetInputWindows(windows); + } + + public void setFocusedApplication(InputApplication application) { + nativeSetFocusedApplication(application); + } + + public void preemptInputDispatch() { + nativePreemptInputDispatch(); + } + + public void setInputDispatchMode(boolean enabled, boolean frozen) { + nativeSetInputDispatchMode(enabled, frozen); + } + public void dump(PrintWriter pw) { // TODO } @@ -344,32 +361,43 @@ public class InputManager { @SuppressWarnings("unused") public void notifyInputChannelBroken(InputChannel inputChannel) { - mWindowManagerService.notifyInputChannelBroken(inputChannel); + mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputChannel); } @SuppressWarnings("unused") public long notifyInputChannelANR(InputChannel inputChannel) { - return mWindowManagerService.notifyInputChannelANR(inputChannel); + return mWindowManagerService.mInputMonitor.notifyInputChannelANR(inputChannel); } @SuppressWarnings("unused") public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) { - mWindowManagerService.notifyInputChannelRecoveredFromANR(inputChannel); + mWindowManagerService.mInputMonitor.notifyInputChannelRecoveredFromANR(inputChannel); + } + + @SuppressWarnings("unused") + public long notifyANR(Object token) { + return mWindowManagerService.mInputMonitor.notifyANR(token); } @SuppressWarnings("unused") - public int hackInterceptKey(int deviceId, int type, int scanCode, + public int interceptKeyBeforeQueueing(int deviceId, int type, int scanCode, int keyCode, int policyFlags, int value, long whenNanos, boolean isScreenOn) { - RawInputEvent event = new RawInputEvent(); - event.deviceId = deviceId; - event.type = type; - event.scancode = scanCode; - event.keycode = keyCode; - event.flags = policyFlags; - event.value = value; - event.when = whenNanos / 1000000; - - return mWindowManagerPolicy.interceptKeyTq(event, isScreenOn); + return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing(deviceId, type, + scanCode, keyCode, policyFlags, value, whenNanos, isScreenOn); + } + + @SuppressWarnings("unused") + public boolean interceptKeyBeforeDispatching(InputChannel focus, int keyCode, + int metaState, boolean down, int repeatCount, int policyFlags) { + return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching(focus, + keyCode, metaState, down, repeatCount, policyFlags); + } + + @SuppressWarnings("unused") + public boolean checkInjectEventsPermission(int injectorPid, int injectorUid) { + return mContext.checkPermission( + android.Manifest.permission.INJECT_EVENTS, injectorPid, injectorUid) + == PackageManager.PERMISSION_GRANTED; } @SuppressWarnings("unused") @@ -379,15 +407,14 @@ public class InputManager { } @SuppressWarnings("unused") - public void pokeUserActivityForKey(long whenNanos) { - long when = whenNanos / 1000000; - mPowerManagerService.userActivity(when, false, - LocalPowerManager.BUTTON_EVENT, false); + public void pokeUserActivity(long eventTimeNanos, int eventType) { + long eventTime = eventTimeNanos / 1000000; + mPowerManagerService.userActivity(eventTime, false, eventType, false); } @SuppressWarnings("unused") public void notifyAppSwitchComing() { - mWindowManagerService.mKeyWaiter.appSwitchComing(); + mWindowManagerService.mInputMonitor.notifyAppSwitchComing(); } @SuppressWarnings("unused") @@ -485,24 +512,5 @@ public class InputManager { return names.toArray(new String[names.size()]); } - - // TODO All code related to target identification should be moved down into native. - @SuppressWarnings("unused") - public int getKeyEventTargets(InputTargetList inputTargets, - KeyEvent event, int nature, int policyFlags, - int injectorPid, int injectorUid) { - inputTargets.clear(); - return mWindowManagerService.getKeyEventTargetsTd( - inputTargets, event, nature, policyFlags, injectorPid, injectorUid); - } - - @SuppressWarnings("unused") - public int getMotionEventTargets(InputTargetList inputTargets, - MotionEvent event, int nature, int policyFlags, - int injectorPid, int injectorUid) { - inputTargets.clear(); - return mWindowManagerService.getMotionEventTargetsTd( - inputTargets, event, nature, policyFlags, injectorPid, injectorUid); - } } } diff --git a/services/java/com/android/server/InputTargetList.java b/services/java/com/android/server/InputTargetList.java deleted file mode 100644 index 83acc8f..0000000 --- a/services/java/com/android/server/InputTargetList.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2010 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.server; - -import android.view.InputChannel; -import android.view.InputTarget; - -/** - * A specialized list of input targets backed by an array. - * - * This class is part of an InputManager optimization to avoid allocating and copying - * input target arrays unnecessarily on return from JNI callbacks. Internally, it keeps - * an array full of demand-allocated InputTarget objects that it recycles each time the - * list is cleared. The used portion of the array is padded with a null. - * - * @hide - */ -public final class InputTargetList { - private InputTarget[] mArray; - private int mCount; - - /** - * Creates an empty input target list. - */ - public InputTargetList() { - mArray = new InputTarget[8]; - } - - /** - * Clears the input target list. - */ - public void clear() { - if (mCount == 0) { - return; - } - - int count = mCount; - mCount = 0; - mArray[count] = mArray[0]; - while (count > 0) { - count -= 1; - mArray[count].recycle(); - } - mArray[0] = null; - } - - /** - * Adds a new input target to the input target list. - * @param inputChannel The input channel of the target window. - * @param flags Input target flags. - * @param timeoutNanos The input dispatch timeout (before ANR) in nanoseconds or -1 if none. - * @param xOffset An offset to add to motion X coordinates during delivery. - * @param yOffset An offset to add to motion Y coordinates during delivery. - */ - public void add(InputChannel inputChannel, int flags, long timeoutNanos, - float xOffset, float yOffset) { - if (inputChannel == null) { - throw new IllegalArgumentException("inputChannel must not be null"); - } - - if (mCount + 1 == mArray.length) { - InputTarget[] oldArray = mArray; - mArray = new InputTarget[oldArray.length * 2]; - System.arraycopy(oldArray, 0, mArray, 0, mCount); - } - - // Grab InputTarget from tail (after used section) if available. - InputTarget inputTarget = mArray[mCount + 1]; - if (inputTarget == null) { - inputTarget = new InputTarget(); - } - inputTarget.mInputChannel = inputChannel; - inputTarget.mFlags = flags; - inputTarget.mTimeoutNanos = timeoutNanos; - inputTarget.mXOffset = xOffset; - inputTarget.mYOffset = yOffset; - - mArray[mCount] = inputTarget; - mCount += 1; - mArray[mCount] = null; - } - - /** - * Gets the input targets as a null-terminated array. - * @return The input target array. - */ - public InputTarget[] toNullTerminatedArray() { - return mArray; - } -}
\ No newline at end of file diff --git a/services/java/com/android/server/InputWindow.java b/services/java/com/android/server/InputWindow.java new file mode 100644 index 0000000..8da0cf1 --- /dev/null +++ b/services/java/com/android/server/InputWindow.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2010 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.server; + +import android.view.InputChannel; + +/** + * Describes input-related window properties for use by the input dispatcher. + * + * @hide + */ +public final class InputWindow { + // The input channel associated with the window. + public InputChannel inputChannel; + + // Window layout params attributes. (WindowManager.LayoutParams) + public int layoutParamsFlags; + public int layoutParamsType; + + // Dispatching timeout. + public long dispatchingTimeoutNanos; + + // Window frame position. + public int frameLeft; + public int frameTop; + + // Window touchable area. + public int touchableAreaLeft; + public int touchableAreaTop; + public int touchableAreaRight; + public int touchableAreaBottom; + + // Window is visible. + public boolean visible; + + // Window has focus. + public boolean hasFocus; + + // Window has wallpaper. (window is the current wallpaper target) + public boolean hasWallpaper; + + // Input event dispatching is paused. + public boolean paused; + + // Id of process and user that owns the window. + public int ownerPid; + public int ownerUid; + + public void recycle() { + inputChannel = null; + } +} diff --git a/services/java/com/android/server/InputWindowList.java b/services/java/com/android/server/InputWindowList.java new file mode 100644 index 0000000..1cbb2cc --- /dev/null +++ b/services/java/com/android/server/InputWindowList.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2010 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.server; + + +/** + * A specialized list of window information objects backed by an array. + * + * This class is part of an InputManager optimization to avoid allocating objects and arrays + * unnecessarily. Internally, it keeps an array full of demand-allocated objects that it + * recycles each time the list is cleared. The used portion of the array is padded with a null. + * + * The contents of the list are intended to be Z-ordered from top to bottom. + * + * @hide + */ +public final class InputWindowList { + private InputWindow[] mArray; + private int mCount; + + /** + * Creates an empty list. + */ + public InputWindowList() { + mArray = new InputWindow[8]; + } + + /** + * Clears the list. + */ + public void clear() { + if (mCount == 0) { + return; + } + + int count = mCount; + mCount = 0; + mArray[count] = mArray[0]; + while (count > 0) { + count -= 1; + mArray[count].recycle(); + } + mArray[0] = null; + } + + /** + * Adds an uninitialized input window object to the list and returns it. + */ + public InputWindow add() { + if (mCount + 1 == mArray.length) { + InputWindow[] oldArray = mArray; + mArray = new InputWindow[oldArray.length * 2]; + System.arraycopy(oldArray, 0, mArray, 0, mCount); + } + + // Grab object from tail (after used section) if available. + InputWindow item = mArray[mCount + 1]; + if (item == null) { + item = new InputWindow(); + } + + mArray[mCount] = item; + mCount += 1; + mArray[mCount] = null; + return item; + } + + /** + * Gets the input window objects as a null-terminated array. + * @return The input window array. + */ + public InputWindow[] toNullTerminatedArray() { + return mArray; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 95ab5bc..bf86b23 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -103,7 +103,6 @@ import android.view.IWindowManager; import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputQueue; -import android.view.InputTarget; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.RawInputEvent; @@ -207,6 +206,9 @@ public class WindowManagerService extends IWindowManager.Stub // Maximum number of milliseconds to wait for input event injection. // FIXME is this value reasonable? private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; + + // Default input dispatching timeout in nanoseconds. + private static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L; static final int INJECT_FAILED = 0; static final int INJECT_SUCCEEDED = 1; @@ -2061,8 +2063,8 @@ public class WindowManagerService extends IWindowManager.Stub boolean focusChanged = false; if (win.canReceiveKeys()) { - if ((focusChanged=updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS)) - == true) { + focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS); + if (focusChanged) { imMayMove = false; } } @@ -2078,10 +2080,9 @@ public class WindowManagerService extends IWindowManager.Stub //dump(); if (focusChanged) { - if (mCurrentFocus != null) { - mKeyWaiter.handleNewWindowLocked(mCurrentFocus); - } + finishUpdateFocusedWindowAfterAssignLayersLocked(); } + if (localLOGV) Slog.v( TAG, "New client " + client.asBinder() + ": window=" + win); @@ -2183,10 +2184,14 @@ public class WindowManagerService extends IWindowManager.Stub } private void removeWindowInnerLocked(Session session, WindowState win) { - mKeyWaiter.finishedKey(session, win.mClient, true, - KeyWaiter.RETURN_NOTHING); - mKeyWaiter.releasePendingPointerLocked(win.mSession); - mKeyWaiter.releasePendingTrackballLocked(win.mSession); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.windowIsBeingRemovedLw(win); + } else { + mKeyWaiter.finishedKey(session, win.mClient, true, + KeyWaiter.RETURN_NOTHING); + mKeyWaiter.releasePendingPointerLocked(win.mSession); + mKeyWaiter.releasePendingTrackballLocked(win.mSession); + } win.mRemoved = true; @@ -2554,8 +2559,12 @@ public class WindowManagerService extends IWindowManager.Stub applyAnimationLocked(win, transit, false)) { focusMayChange = true; win.mExiting = true; - mKeyWaiter.finishedKey(session, client, true, - KeyWaiter.RETURN_NOTHING); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.windowIsBecomingInvisibleLw(win); + } else { + mKeyWaiter.finishedKey(session, client, true, + KeyWaiter.RETURN_NOTHING); + } } else if (win.isAnimating()) { // Currently in a hide animation... turn this into // an exit. @@ -3016,8 +3025,12 @@ public class WindowManagerService extends IWindowManager.Stub if (win.isVisibleNow()) { applyAnimationLocked(win, WindowManagerPolicy.TRANSIT_EXIT, false); - mKeyWaiter.finishedKey(win.mSession, win.mClient, true, - KeyWaiter.RETURN_NOTHING); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.windowIsBeingRemovedLw(win); + } else { + mKeyWaiter.finishedKey(win.mSession, win.mClient, true, + KeyWaiter.RETURN_NOTHING); + } changed = true; } } @@ -3048,6 +3061,20 @@ public class WindowManagerService extends IWindowManager.Stub "addAppToken()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } + + // Get the dispatching timeout here while we are not holding any locks so that it + // can be cached by the AppWindowToken. The timeout value is used later by the + // input dispatcher in code that does hold locks. If we did not cache the value + // here we would run the chance of introducing a deadlock between the window manager + // (which holds locks while updating the input dispatcher state) and the activity manager + // (which holds locks while querying the application token). + long inputDispatchingTimeoutNanos; + try { + inputDispatchingTimeoutNanos = token.getKeyDispatchingTimeout() * 1000000L; + } catch (RemoteException ex) { + Slog.w(TAG, "Could not get dispatching timeout.", ex); + inputDispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + } synchronized(mWindowMap) { AppWindowToken wtoken = findAppWindowToken(token.asBinder()); @@ -3056,6 +3083,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } wtoken = new AppWindowToken(token); + wtoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; wtoken.groupId = groupId; wtoken.appFullscreen = fullscreen; wtoken.requestedOrientation = requestedOrientation; @@ -3319,7 +3347,13 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_FOCUS) Slog.v(TAG, "Clearing focused app, was " + mFocusedApp); changed = mFocusedApp != null; mFocusedApp = null; - mKeyWaiter.tickle(); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + if (changed) { + mInputMonitor.setFocusedAppLw(null); + } + } else { + mKeyWaiter.tickle(); + } } else { AppWindowToken newFocus = findAppWindowToken(token); if (newFocus == null) { @@ -3329,7 +3363,13 @@ public class WindowManagerService extends IWindowManager.Stub changed = mFocusedApp != newFocus; mFocusedApp = newFocus; if (DEBUG_FOCUS) Slog.v(TAG, "Set focused app to: " + mFocusedApp); - mKeyWaiter.tickle(); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + if (changed) { + mInputMonitor.setFocusedAppLw(newFocus); + } + } else { + mKeyWaiter.tickle(); + } } if (moveFocusNow && changed) { @@ -3640,8 +3680,12 @@ public class WindowManagerService extends IWindowManager.Stub applyAnimationLocked(win, WindowManagerPolicy.TRANSIT_EXIT, false); } - mKeyWaiter.finishedKey(win.mSession, win.mClient, true, - KeyWaiter.RETURN_NOTHING); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.windowIsBecomingInvisibleLw(win); + } else { + mKeyWaiter.finishedKey(win.mSession, win.mClient, true, + KeyWaiter.RETURN_NOTHING); + } changed = true; } } @@ -3926,7 +3970,11 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_FOCUS) Slog.v(TAG, "Removing focused app token:" + wtoken); mFocusedApp = null; updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL); - mKeyWaiter.tickle(); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.setFocusedAppLw(null); + } else { + mKeyWaiter.tickle(); + } } } else { Slog.w(TAG, "Attempted to remove non-existing app token: " + token); @@ -5075,456 +5123,375 @@ public class WindowManagerService extends IWindowManager.Stub return true; } - /* Notifies the window manager about a broken input channel. - * - * Called by the InputManager. - */ - public void notifyInputChannelBroken(InputChannel inputChannel) { - synchronized (mWindowMap) { - WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); - if (windowState == null) { - return; // irrelevant - } - - Slog.i(TAG, "WINDOW DIED " + windowState); - removeWindowLocked(windowState.mSession, windowState); - } - } - - /* Notifies the window manager about a broken input channel. - * - * Called by the InputManager. - */ - public long notifyInputChannelANR(InputChannel inputChannel) { - IApplicationToken appToken; - synchronized (mWindowMap) { - WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); - if (windowState == null) { - return -2; // irrelevant, abort dispatching (-2) - } - - Slog.i(TAG, "Input event dispatching timed out sending to " - + windowState.mAttrs.getTitle()); - appToken = windowState.getAppToken(); - } - - try { - // Notify the activity manager about the timeout and let it decide whether - // to abort dispatching or keep waiting. - boolean abort = appToken.keyDispatchingTimedOut(); - if (abort) { - return -2; // abort dispatching - } - - // Return new timeout. - // We use -1 for infinite timeout to avoid clash with -2 magic number. - long newTimeout = appToken.getKeyDispatchingTimeout() * 1000000; - return newTimeout < 0 ? -1 : newTimeout; - } catch (RemoteException ex) { - return -2; // abort dispatching - } - } - - /* Notifies the window manager about a broken input channel. - * - * Called by the InputManager. - */ - public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) { - // Nothing to do just now. - // Just wait for the user to dismiss the ANR dialog. - - // TODO We could try to automatically dismiss the ANR dialog on recovery - // although that might be disorienting. - } - - private WindowState getWindowStateForInputChannelLocked(InputChannel inputChannel) { - int windowCount = mWindows.size(); - for (int i = 0; i < windowCount; i++) { - WindowState windowState = (WindowState) mWindows.get(i); - if (windowState.mInputChannel == inputChannel) { - return windowState; - } - } - - return null; - } - // ------------------------------------------------------------- // Input Events and Focus Management // ------------------------------------------------------------- - private boolean checkInjectionPermissionTd(WindowState focus, - int injectorPid, int injectorUid) { - if (injectorUid > 0 && (focus == null || injectorUid != focus.mSession.mUid)) { - if (mContext.checkPermission( - android.Manifest.permission.INJECT_EVENTS, injectorPid, injectorUid) - != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission denied: injecting key event from pid " - + injectorPid + " uid " + injectorUid + " to window " + focus - + " owned by uid " + focus.mSession.mUid); - return false; - } - } - return true; - } + InputMonitor mInputMonitor = new InputMonitor(); - /* Gets the input targets for a key event. - * - * Called by the InputManager on the InputDispatcher thread. - */ - public int getKeyEventTargetsTd(InputTargetList inputTargets, - KeyEvent event, int nature, int policyFlags, int injectorPid, int injectorUid) { - if (DEBUG_INPUT) Slog.v(TAG, "Dispatch key: " + event); - - // TODO what do we do with mDisplayFrozen? - // TODO what do we do with focus.mToken.paused? + /* Tracks the progress of input dispatch and ensures that input dispatch state + * is kept in sync with changes in window focus, visibility, registration, and + * other relevant Window Manager state transitions. */ + final class InputMonitor { + // Current window with input focus for keys and other non-touch events. May be null. + private WindowState mInputFocus; - WindowState focus = getFocusedWindow(); + // When true, prevents input dispatch from proceeding until set to false again. + private boolean mInputDispatchFrozen; - if (! checkInjectionPermissionTd(focus, injectorPid, injectorUid)) { - return InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED; - } + // When true, input dispatch proceeds normally. Otherwise all events are dropped. + private boolean mInputDispatchEnabled = true; + + // Temporary list of windows information to provide to the input dispatcher. + private InputWindowList mTempInputWindows = new InputWindowList(); - if (mPolicy.interceptKeyTi(focus, event.getKeyCode(), event.getMetaState(), - event.getAction() == KeyEvent.ACTION_DOWN, - event.getRepeatCount(), event.getFlags())) { - // Policy consumed the event. - return InputManager.INPUT_EVENT_INJECTION_SUCCEEDED; - } + // Temporary input application object to provide to the input dispatcher. + private InputApplication mTempInputApplication = new InputApplication(); - if (focus == null) { - return InputManager.INPUT_EVENT_INJECTION_FAILED; + /* Notifies the window manager about a broken input channel. + * + * Called by the InputManager. + */ + public void notifyInputChannelBroken(InputChannel inputChannel) { + synchronized (mWindowMap) { + WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); + if (windowState == null) { + return; // irrelevant + } + + Slog.i(TAG, "WINDOW DIED " + windowState); + removeWindowLocked(windowState.mSession, windowState); + } } - wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT); - - addInputTargetTd(inputTargets, focus, InputTarget.FLAG_SYNC); - return InputManager.INPUT_EVENT_INJECTION_SUCCEEDED; - } - - /* Gets the input targets for a motion event. - * - * Called by the InputManager on the InputDispatcher thread. - */ - public int getMotionEventTargetsTd(InputTargetList inputTargets, - MotionEvent event, int nature, int policyFlags, int injectorPid, int injectorUid) { - switch (nature) { - case InputQueue.INPUT_EVENT_NATURE_TRACKBALL: - return getMotionEventTargetsForTrackballTd(inputTargets, event, policyFlags, - injectorPid, injectorUid); - - case InputQueue.INPUT_EVENT_NATURE_TOUCH: - return getMotionEventTargetsForTouchTd(inputTargets, event, policyFlags, - injectorPid, injectorUid); + /* Notifies the window manager about an input channel that is not responding. + * The method can either cause dispatching to be aborted by returning -2 or + * return a new timeout in nanoseconds. + * + * Called by the InputManager. + */ + public long notifyInputChannelANR(InputChannel inputChannel) { + AppWindowToken token; + synchronized (mWindowMap) { + WindowState windowState = getWindowStateForInputChannelLocked(inputChannel); + if (windowState == null) { + return -2; // irrelevant, abort dispatching (-2) + } - default: - return InputManager.INPUT_EVENT_INJECTION_FAILED; + Slog.i(TAG, "Input event dispatching timed out sending to " + + windowState.mAttrs.getTitle()); + token = windowState.mAppToken; + } + + return notifyANRInternal(token); } - } - /* Gets the input targets for a trackball event. - * - * Called by the InputManager on the InputDispatcher thread. - */ - private int getMotionEventTargetsForTrackballTd(InputTargetList inputTargets, - MotionEvent event, int policyFlags, int injectorPid, int injectorUid) { - WindowState focus = getFocusedWindow(); - - if (! checkInjectionPermissionTd(focus, injectorPid, injectorUid)) { - return InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED; - } - - if (focus == null) { - return InputManager.INPUT_EVENT_INJECTION_FAILED; + /* Notifies the window manager about an input channel spontaneously recovering from ANR + * by successfully delivering the event that originally timed out. + * + * Called by the InputManager. + */ + public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) { + // Nothing to do just now. + // Just wait for the user to dismiss the ANR dialog. } - wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT); - - addInputTargetTd(inputTargets, focus, InputTarget.FLAG_SYNC); - return InputManager.INPUT_EVENT_INJECTION_SUCCEEDED; - } - - /* Set to true when a fat touch has been detected during the processing of a touch event. - * - * Only used by getMotionEventTargetsForTouchTd. - * Set to true whenever a fat touch is detected and reset to false on ACTION_UP. - */ - private boolean mFatTouch; - - /* Set to true when we think the touch event. - * - * Only used by getMotionEventTargetsForTouchTd. - * Set to true on ACTION_DOWN and set to false on ACTION_UP. - */ - private boolean mTouchDown; - - /* Current target of Motion events. - * - * Only used by getMotionEventTargetsForTouchTd. - * Initialized on ACTION_DOWN and cleared on ACTION_UP. - */ - private WindowState mTouchFocus; + /* Notifies the window manager about an application that is not responding + * in general rather than with respect to a particular input channel. + * The method can either cause dispatching to be aborted by returning -2 or + * return a new timeout in nanoseconds. + * + * Called by the InputManager. + */ + public long notifyANR(Object token) { + AppWindowToken appWindowToken = (AppWindowToken) token; - /* Windows above the target that would like to receive an "outside" touch event - * for any down events outside of them. - * - * Only used by getMotionEventTargetsForTouchTd. - * Initialized on ACTION_DOWN and cleared immediately afterwards. - */ - private ArrayList<WindowState> mOutsideTouchTargets = new ArrayList<WindowState>(); - - /* Wallpaper windows that are currently receiving touch events. - * - * Only used by getMotionEventTargetsForTouchTd. - * Initialized on ACTION_DOWN and cleared on ACTION_UP. - */ - private ArrayList<WindowState> mWallpaperTouchTargets = new ArrayList<WindowState>(); - - /* Gets the input targets for a touch event. - * - * Called by the InputManager on the InputDispatcher thread. - */ - private int getMotionEventTargetsForTouchTd(InputTargetList inputTargets, - MotionEvent event, int policyFlags, int injectorPid, int injectorUid) { - final int action = event.getAction(); - - if (action == MotionEvent.ACTION_DOWN) { - updateTouchFocusBeforeDownTd(event, policyFlags); - } else { - updateTouchFocusBeforeNonDownTd(event, policyFlags); + Slog.i(TAG, "Input event dispatching timed out sending to application " + + appWindowToken.stringName); + return notifyANRInternal(appWindowToken); } - - boolean skipDelivery = false; - int touchTargetFlags = 0; - int injectionResult = InputManager.INPUT_EVENT_INJECTION_SUCCEEDED; - WindowState focusedTouchTarget = mTouchFocus; - if (focusedTouchTarget == null) { - // In this case we are either dropping the event, or have received - // a move or up without a down. It is common to receive move - // events in such a way, since this means the user is moving the - // pointer without actually pressing down. All other cases should - // be atypical, so let's log them. - if (action != MotionEvent.ACTION_MOVE) { - Slog.w(TAG, "No window to dispatch pointer action " + action); - injectionResult = InputManager.INPUT_EVENT_INJECTION_FAILED; - } - } else { - // We have a valid focused touch target. - if (! checkInjectionPermissionTd(focusedTouchTarget, injectorPid, injectorUid)) { - return InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED; - } - - wakeupIfNeeded(focusedTouchTarget, eventType(event)); - - if ((focusedTouchTarget.mAttrs.flags & - WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) != 0) { - // Target wants to ignore fat touch events - boolean cheekPress = mPolicy.isCheekPressedAgainstScreen(event); - - if (cheekPress) { - if ((action == MotionEvent.ACTION_DOWN)) { - mFatTouch = true; - skipDelivery = true; - } else { - if (! mFatTouch) { - // cancel the earlier event - touchTargetFlags |= InputTarget.FLAG_CANCEL; - mFatTouch = true; - } else { - skipDelivery = true; - } + private long notifyANRInternal(AppWindowToken token) { + if (token != null && token.appToken != null) { + try { + // Notify the activity manager about the timeout and let it decide whether + // to abort dispatching or keep waiting. + boolean abort = token.appToken.keyDispatchingTimedOut(); + if (! abort) { + // The activity manager declined to abort dispatching. + // Wait a bit longer and timeout again later. + return token.inputDispatchingTimeoutNanos; } + } catch (RemoteException ex) { } } + return -2; // abort dispatching } - if (! skipDelivery) { - int outsideTargetCount = mOutsideTouchTargets.size(); - for (int i = 0; i < outsideTargetCount; i++) { - WindowState outsideTouchTarget = mOutsideTouchTargets.get(i); - addInputTargetTd(inputTargets, outsideTouchTarget, - InputTarget.FLAG_OUTSIDE | touchTargetFlags); - } - - int wallpaperTargetCount = mWallpaperTouchTargets.size(); - for (int i = 0; i < wallpaperTargetCount; i++) { - WindowState wallpaperTouchTarget = mWallpaperTouchTargets.get(i); - addInputTargetTd(inputTargets, wallpaperTouchTarget, - touchTargetFlags); - } - - if (focusedTouchTarget != null) { - addInputTargetTd(inputTargets, focusedTouchTarget, - InputTarget.FLAG_SYNC | touchTargetFlags); + private WindowState getWindowStateForInputChannel(InputChannel inputChannel) { + synchronized (mWindowMap) { + return getWindowStateForInputChannelLocked(inputChannel); } } - - if (action == MotionEvent.ACTION_UP) { - updateTouchFocusAfterUpTd(event, policyFlags); - } - return injectionResult; - } - - private void updateTouchFocusBeforeDownTd(MotionEvent event, int policyFlags) { - if (mTouchDown) { - // This is weird, we got a down, but we thought it was already down! - // XXX: We should probably send an ACTION_UP to the current target. - Slog.w(TAG, "Pointer down received while already down in: " + mTouchFocus); - updateTouchFocusAfterUpTd(event, policyFlags); + private WindowState getWindowStateForInputChannelLocked(InputChannel inputChannel) { + int windowCount = mWindows.size(); + for (int i = 0; i < windowCount; i++) { + WindowState windowState = (WindowState) mWindows.get(i); + if (windowState.mInputChannel == inputChannel) { + return windowState; + } + } + + return null; } - mTouchDown = true; - mPowerManager.logPointerDownEvent(); - - final boolean screenWasOff = (policyFlags & WindowManagerPolicy.FLAG_BRIGHT_HERE) != 0; - synchronized (mWindowMap) { - final int x = (int) event.getX(); - final int y = (int) event.getY(); - + /* Updates the cached window information provided to the input dispatcher. */ + public void updateInputWindowsLw() { + // Populate the input window list with information about all of the windows that + // could potentially receive input. + // As an optimization, we could try to prune the list of windows but this turns + // out to be difficult because only the native code knows for sure which window + // currently has touch focus. final ArrayList windows = mWindows; final int N = windows.size(); - WindowState topErrWindow = null; - final Rect tmpRect = mTempRect; - for (int i= N - 1; i >= 0; i--) { - WindowState child = (WindowState) windows.get(i); - //Slog.i(TAG, "Checking dispatch to: " + child); + for (int i = N - 1; i >= 0; i--) { + final WindowState child = (WindowState) windows.get(i); + if (child.mInputChannel == null) { + // Skip this window because it cannot possibly receive input. + continue; + } final int flags = child.mAttrs.flags; - if ((flags & WindowManager.LayoutParams.FLAG_SYSTEM_ERROR) != 0) { - if (topErrWindow == null) { - topErrWindow = child; - } - } + final int type = child.mAttrs.type; - if (!child.isVisibleLw()) { - //Slog.i(TAG, "Not visible!"); - continue; - } + final boolean hasFocus = (child == mInputFocus); + final boolean isVisible = child.isVisibleLw(); + final boolean hasWallpaper = (child == mWallpaperTarget) + && (type != WindowManager.LayoutParams.TYPE_KEYGUARD); - if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) == 0) { - tmpRect.set(child.mFrame); - if (child.mTouchableInsets == ViewTreeObserver - .InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) { - // The touch is inside of the window if it is - // inside the frame, AND the content part of that - // frame that was given by the application. - tmpRect.left += child.mGivenContentInsets.left; - tmpRect.top += child.mGivenContentInsets.top; - tmpRect.right -= child.mGivenContentInsets.right; - tmpRect.bottom -= child.mGivenContentInsets.bottom; - } else if (child.mTouchableInsets == ViewTreeObserver - .InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE) { - // The touch is inside of the window if it is - // inside the frame, AND the visible part of that - // frame that was given by the application. - tmpRect.left += child.mGivenVisibleInsets.left; - tmpRect.top += child.mGivenVisibleInsets.top; - tmpRect.right -= child.mGivenVisibleInsets.right; - tmpRect.bottom -= child.mGivenVisibleInsets.bottom; - } - final int touchFlags = flags & - (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); - if (tmpRect.contains(x, y) || touchFlags == 0) { - //Slog.i(TAG, "Using this target!"); - if (! screenWasOff || (flags & - WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING) != 0) { - mTouchFocus = child; - } else { - //Slog.i(TAG, "Waking, skip!"); - mTouchFocus = null; - } + // Add a window to our list of input windows. + final InputWindow inputWindow = mTempInputWindows.add(); + inputWindow.inputChannel = child.mInputChannel; + inputWindow.layoutParamsFlags = flags; + inputWindow.layoutParamsType = type; + inputWindow.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos(); + inputWindow.visible = isVisible; + inputWindow.hasFocus = hasFocus; + inputWindow.hasWallpaper = hasWallpaper; + inputWindow.paused = child.mAppToken != null ? child.mAppToken.paused : false; + inputWindow.ownerPid = child.mSession.mPid; + inputWindow.ownerUid = child.mSession.mUid; + + final Rect frame = child.mFrame; + inputWindow.frameLeft = frame.left; + inputWindow.frameTop = frame.top; + + switch (child.mTouchableInsets) { + default: + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: + inputWindow.touchableAreaLeft = frame.left; + inputWindow.touchableAreaTop = frame.top; + inputWindow.touchableAreaRight = frame.right; + inputWindow.touchableAreaBottom = frame.bottom; + break; + + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: { + Rect inset = child.mGivenContentInsets; + inputWindow.touchableAreaLeft = frame.left + inset.left; + inputWindow.touchableAreaTop = frame.top + inset.top; + inputWindow.touchableAreaRight = frame.right - inset.right; + inputWindow.touchableAreaBottom = frame.bottom - inset.bottom; + break; + } + + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: { + Rect inset = child.mGivenVisibleInsets; + inputWindow.touchableAreaLeft = frame.left + inset.left; + inputWindow.touchableAreaTop = frame.top + inset.top; + inputWindow.touchableAreaRight = frame.right - inset.right; + inputWindow.touchableAreaBottom = frame.bottom - inset.bottom; break; } - } - - if ((flags & WindowManager.LayoutParams - .FLAG_WATCH_OUTSIDE_TOUCH) != 0) { - //Slog.i(TAG, "Adding to outside target list: " + child); - mOutsideTouchTargets.add(child); } } - // If there's an error window but it's not accepting focus (typically because - // it is not yet visible) just wait for it -- any other focused window may in fact - // be in ANR state. - if (topErrWindow != null && mTouchFocus != topErrWindow) { - mTouchFocus = null; - } + // Send windows to native code. + mInputManager.setInputWindows(mTempInputWindows.toNullTerminatedArray()); - // Drop the touch focus if the window is not visible. - if (mTouchFocus != null && ! mTouchFocus.isVisibleLw()) { - mTouchFocus = null; - } + // Clear the list in preparation for the next round. + // Also avoids keeping InputChannel objects referenced unnecessarily. + mTempInputWindows.clear(); + } + + /* Notifies that an app switch key (BACK / HOME) has just been pressed. + * This essentially starts a .5 second timeout for the application to process + * subsequent input events while waiting for the app switch to occur. If it takes longer + * than this, the pending events will be dropped. + */ + public void notifyAppSwitchComing() { + // TODO Not implemented yet. Should go in the native side. + } + + /* Provides an opportunity for the window manager policy to intercept early key + * processing as soon as the key has been read from the device. */ + public int interceptKeyBeforeQueueing(int deviceId, int type, int scanCode, + int keyCode, int policyFlags, int value, long whenNanos, boolean isScreenOn) { + RawInputEvent event = new RawInputEvent(); + event.deviceId = deviceId; + event.type = type; + event.scancode = scanCode; + event.keycode = keyCode; + event.flags = policyFlags; + event.value = value; + event.when = whenNanos / 1000000; - // Determine wallpaper targets. - if (mTouchFocus != null - && mTouchFocus == mWallpaperTarget - && mTouchFocus.mAttrs.type != WindowManager.LayoutParams.TYPE_KEYGUARD) { - int curTokenIndex = mWallpaperTokens.size(); - while (curTokenIndex > 0) { - curTokenIndex--; - WindowToken token = mWallpaperTokens.get(curTokenIndex); - - int curWallpaperIndex = token.windows.size(); - while (curWallpaperIndex > 0) { - curWallpaperIndex--; - WindowState wallpaper = token.windows.get(curWallpaperIndex); - if ((wallpaper.mAttrs.flags & - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) == 0) { - mWallpaperTouchTargets.add(wallpaper); + return mPolicy.interceptKeyTq(event, isScreenOn); + } + + /* Provides an opportunity for the window manager policy to process a key before + * ordinary dispatch. */ + public boolean interceptKeyBeforeDispatching(InputChannel focus, int keyCode, + int metaState, boolean down, int repeatCount, int policyFlags) { + WindowState windowState = getWindowStateForInputChannel(focus); + return mPolicy.interceptKeyTi(windowState, keyCode, metaState, down, repeatCount, + policyFlags); + } + + /* Called when the current input focus changes. + * Layer assignment is assumed to be complete by the time this is called. + */ + public void setInputFocusLw(WindowState newWindow) { + if (DEBUG_INPUT) { + Slog.d(TAG, "Input focus has changed to " + newWindow); + } + + if (newWindow != mInputFocus) { + if (newWindow != null && newWindow.canReceiveKeys()) { + // If the new input focus is an error window or appears above the current + // input focus, preempt any pending synchronous dispatch so that we can + // start delivering events to the new input focus as soon as possible. + if ((newWindow.mAttrs.flags & FLAG_SYSTEM_ERROR) != 0) { + if (DEBUG_INPUT) { + Slog.v(TAG, "New SYSTEM_ERROR window; resetting state"); } + preemptInputDispatchLw(); + } else if (mInputFocus != null && newWindow.mLayer > mInputFocus.mLayer) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Transferring focus to new window at higher layer: " + + "old win layer=" + mInputFocus.mLayer + + ", new win layer=" + newWindow.mLayer); + } + preemptInputDispatchLw(); } + + // Displaying a window implicitly causes dispatching to be unpaused. + // This is to protect against bugs if someone pauses dispatching but + // forgets to resume. + newWindow.mToken.paused = false; } + + mInputFocus = newWindow; + updateInputWindowsLw(); } } - } - - private void updateTouchFocusBeforeNonDownTd(MotionEvent event, int policyFlags) { - synchronized (mWindowMap) { - // Drop the touch focus if the window is not visible. - if (mTouchFocus != null && ! mTouchFocus.isVisibleLw()) { - mTouchFocus = null; - mWallpaperTouchTargets.clear(); + + public void windowIsBecomingInvisibleLw(WindowState window) { + // The window is becoming invisible. Preempt input dispatch in progress + // so that the next window below can receive focus. + if (window == mInputFocus) { + mInputFocus = null; + preemptInputDispatchLw(); } + + updateInputWindowsLw(); } - } - - private void updateTouchFocusAfterUpTd(MotionEvent event, int policyFlags) { - mFatTouch = false; - mTouchDown = false; - mTouchFocus = null; - mOutsideTouchTargets.clear(); - mWallpaperTouchTargets.clear(); - mPowerManager.logPointerUpEvent(); - } - - /* Adds a window to a list of input targets. - * Do NOT call this method while holding any locks because the call to - * appToken.getKeyDispatchingTimeout() can potentially call into the ActivityManager - * and create a deadlock hazard. - */ - private void addInputTargetTd(InputTargetList inputTargets, WindowState window, int flags) { - if (window.mInputChannel == null) { - return; + /* Tells the dispatcher to stop waiting for its current synchronous event targets. + * Essentially, just makes those dispatches asynchronous so a new dispatch cycle + * can begin. + */ + private void preemptInputDispatchLw() { + mInputManager.preemptInputDispatch(); } - long timeoutNanos = -1; - IApplicationToken appToken = window.getAppToken(); - - if (appToken != null) { - try { - timeoutNanos = appToken.getKeyDispatchingTimeout() * 1000000; - } catch (RemoteException ex) { - Slog.w(TAG, "Could not get key dispatching timeout.", ex); + public void setFocusedAppLw(AppWindowToken newApp) { + // Focused app has changed. + if (newApp == null) { + mInputManager.setFocusedApplication(null); + } else { + mTempInputApplication.name = newApp.toString(); + mTempInputApplication.dispatchingTimeoutNanos = + newApp.inputDispatchingTimeoutNanos; + mTempInputApplication.token = newApp; + + mInputManager.setFocusedApplication(mTempInputApplication); + } + } + + public void windowIsBeingRemovedLw(WindowState window) { + // Window is being removed. + updateInputWindowsLw(); + } + + public void pauseDispatchingLw(WindowToken window) { + if (! window.paused) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Pausing WindowToken " + window); + } + + window.paused = true; + updateInputWindowsLw(); + } + } + + public void resumeDispatchingLw(WindowToken window) { + if (window.paused) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Resuming WindowToken " + window); + } + + window.paused = false; + updateInputWindowsLw(); } } - inputTargets.add(window.mInputChannel, flags, timeoutNanos, - - window.mFrame.left, - window.mFrame.top); + public void freezeInputDispatchingLw() { + if (! mInputDispatchFrozen) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Freezing input dispatching"); + } + + mInputDispatchFrozen = true; + updateInputDispatchModeLw(); + } + } + + public void thawInputDispatchingLw() { + if (mInputDispatchFrozen) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Thawing input dispatching"); + } + + mInputDispatchFrozen = false; + updateInputDispatchModeLw(); + } + } + + public void setEventDispatchingLw(boolean enabled) { + if (mInputDispatchEnabled != enabled) { + if (DEBUG_INPUT) { + Slog.v(TAG, "Setting event dispatching to " + enabled); + } + + mInputDispatchEnabled = enabled; + updateInputDispatchModeLw(); + } + } + + private void updateInputDispatchModeLw() { + mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen); + } } private final void wakeupIfNeeded(WindowState targetWin, int eventType) { @@ -5582,6 +5549,8 @@ public class WindowManagerService extends IWindowManager.Stub return OTHER_EVENT; } } + + private boolean mFatTouch; // remove me together with dispatchPointer /** * @return Returns true if event was dispatched, false if it was dropped for any reason @@ -5978,7 +5947,11 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { WindowToken token = mTokenMap.get(_token); if (token != null) { - mKeyWaiter.pauseDispatchingLocked(token); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.pauseDispatchingLw(token); + } else { + mKeyWaiter.pauseDispatchingLocked(token); + } } } } @@ -5992,7 +5965,11 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { WindowToken token = mTokenMap.get(_token); if (token != null) { - mKeyWaiter.resumeDispatchingLocked(token); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.resumeDispatchingLw(token); + } else { + mKeyWaiter.resumeDispatchingLocked(token); + } } } } @@ -6004,7 +5981,11 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized (mWindowMap) { - mKeyWaiter.setEventDispatchingLocked(enabled); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.setEventDispatchingLw(enabled); + } else { + mKeyWaiter.setEventDispatchingLocked(enabled); + } } } @@ -7383,6 +7364,9 @@ public class WindowManagerService extends IWindowManager.Stub public void finishKey(IWindow window) { if (localLOGV) Slog.v( TAG, "IWindow finishKey called for " + window); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + throw new IllegalStateException("Should not be called anymore."); + } mKeyWaiter.finishedKey(this, window, false, KeyWaiter.RETURN_NOTHING); } @@ -7390,6 +7374,9 @@ public class WindowManagerService extends IWindowManager.Stub public MotionEvent getPendingPointerMove(IWindow window) { if (localLOGV) Slog.v( TAG, "IWindow getPendingMotionEvent called for " + window); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + throw new IllegalStateException("Should not be called anymore."); + } return mKeyWaiter.finishedKey(this, window, false, KeyWaiter.RETURN_PENDING_POINTER); } @@ -7397,6 +7384,9 @@ public class WindowManagerService extends IWindowManager.Stub public MotionEvent getPendingTrackballMove(IWindow window) { if (localLOGV) Slog.v( TAG, "IWindow getPendingMotionEvent called for " + window); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + throw new IllegalStateException("Should not be called anymore."); + } return mKeyWaiter.finishedKey(this, window, false, KeyWaiter.RETURN_PENDING_TRACKBALL); } @@ -7943,6 +7933,12 @@ public class WindowManagerService extends IWindowManager.Stub public IApplicationToken getAppToken() { return mAppToken != null ? mAppToken.appToken : null; } + + public long getInputDispatchingTimeoutNanos() { + return mAppToken != null + ? mAppToken.inputDispatchingTimeoutNanos + : DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; + } public boolean hasAppShownWindows() { return mAppToken != null ? mAppToken.firstWindowDrawn : false; @@ -8079,10 +8075,12 @@ public class WindowManagerService extends IWindowManager.Stub // Window is no longer on-screen, so can no longer receive // key events... if we were waiting for it to finish // handling a key event, the wait is over! - mKeyWaiter.finishedKey(mSession, mClient, true, - KeyWaiter.RETURN_NOTHING); - mKeyWaiter.releasePendingPointerLocked(mSession); - mKeyWaiter.releasePendingTrackballLocked(mSession); + if (! ENABLE_NATIVE_INPUT_DISPATCH) { + mKeyWaiter.finishedKey(mSession, mClient, true, + KeyWaiter.RETURN_NOTHING); + mKeyWaiter.releasePendingPointerLocked(mSession); + mKeyWaiter.releasePendingTrackballLocked(mSession); + } if (mAppToken != null && this == mAppToken.startingWindow) { mAppToken.startingDisplayed = false; @@ -8098,6 +8096,10 @@ public class WindowManagerService extends IWindowManager.Stub i--; WindowState c = (WindowState)mChildWindows.get(i); c.mAttachedHidden = true; + + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.windowIsBecomingInvisibleLw(c); + } } if (mReportDestroySurface) { @@ -8405,7 +8407,14 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Error hiding surface in " + this, e); } mLastHidden = true; - mKeyWaiter.releasePendingPointerLocked(mSession); + + if (ENABLE_NATIVE_INPUT_DISPATCH) { + for (int i=0; i<N; i++) { + mInputMonitor.windowIsBecomingInvisibleLw((WindowState)mChildWindows.get(i)); + } + } else { + mKeyWaiter.releasePendingPointerLocked(mSession); + } } mExiting = false; if (mRemoveOnExit) { @@ -9098,6 +9107,9 @@ public class WindowManagerService extends IWindowManager.Stub int groupId = -1; boolean appFullscreen; int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + + // The input dispatching timeout for this application token in nanoseconds. + long inputDispatchingTimeoutNanos; // These are used for determining when all windows associated with // an activity have been drawn, so they can be made visible together @@ -10158,6 +10170,11 @@ public class WindowManagerService extends IWindowManager.Stub } } } + + // Window frames may have changed. Tell the input dispatcher about it. + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.updateInputWindowsLw(); + } return mPolicy.finishLayoutLw(); } @@ -10958,7 +10975,11 @@ public class WindowManagerService extends IWindowManager.Stub Slog.w(TAG, "Exception hiding surface in " + w); } } - mKeyWaiter.releasePendingPointerLocked(w.mSession); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.windowIsBecomingInvisibleLw(w); + } else { + mKeyWaiter.releasePendingPointerLocked(w.mSession); + } } // If we are waiting for this window to handle an // orientation change, well, it is hidden, so @@ -11548,14 +11569,26 @@ public class WindowManagerService extends IWindowManager.Stub assignLayersLocked(); } } - - if (newFocus != null && mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { - mKeyWaiter.handleNewWindowLocked(newFocus); + + if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) { + // If we defer assigning layers, then the caller is responsible for + // doing this part. + finishUpdateFocusedWindowAfterAssignLayersLocked(); } return true; } return false; } + + private void finishUpdateFocusedWindowAfterAssignLayersLocked() { + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.setInputFocusLw(mCurrentFocus); + } else { + if (mCurrentFocus != null) { + mKeyWaiter.handleNewWindowLocked(mCurrentFocus); + } + } + } private WindowState computeFocusedWindowLocked() { WindowState result = null; @@ -11634,8 +11667,10 @@ public class WindowManagerService extends IWindowManager.Stub // still frozen, the events will continue to be blocked while the // successive orientation change is processed. To prevent spurious // ANRs, we reset the event dispatch timeout in this case. - synchronized (mKeyWaiter) { - mKeyWaiter.mWasFrozen = true; + if (! ENABLE_NATIVE_INPUT_DISPATCH) { + synchronized (mKeyWaiter) { + mKeyWaiter.mWasFrozen = true; + } } return; } @@ -11658,6 +11693,11 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException()); mDisplayFrozen = true; + + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.freezeInputDispatchingLw(); + } + if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET; mNextAppTransitionPackage = null; @@ -11691,9 +11731,13 @@ public class WindowManagerService extends IWindowManager.Stub // Reset the key delivery timeout on unfreeze, too. We force a wakeup here // too because regular key delivery processing should resume immediately. - synchronized (mKeyWaiter) { - mKeyWaiter.mWasFrozen = true; - mKeyWaiter.notifyAll(); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputMonitor.thawInputDispatchingLw(); + } else { + synchronized (mKeyWaiter) { + mKeyWaiter.mWasFrozen = true; + mKeyWaiter.notifyAll(); + } } // While the display is frozen we don't re-compute the orientation @@ -11949,20 +11993,25 @@ public class WindowManagerService extends IWindowManager.Stub } pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth()); pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight()); - pw.println(" KeyWaiter state:"); - pw.print(" mLastWin="); pw.print(mKeyWaiter.mLastWin); - pw.print(" mLastBinder="); pw.println(mKeyWaiter.mLastBinder); - pw.print(" mFinished="); pw.print(mKeyWaiter.mFinished); - pw.print(" mGotFirstWindow="); pw.print(mKeyWaiter.mGotFirstWindow); - pw.print(" mEventDispatching="); pw.print(mKeyWaiter.mEventDispatching); - pw.print(" mTimeToSwitch="); pw.println(mKeyWaiter.mTimeToSwitch); + + if (! ENABLE_NATIVE_INPUT_DISPATCH) { + pw.println(" KeyWaiter state:"); + pw.print(" mLastWin="); pw.print(mKeyWaiter.mLastWin); + pw.print(" mLastBinder="); pw.println(mKeyWaiter.mLastBinder); + pw.print(" mFinished="); pw.print(mKeyWaiter.mFinished); + pw.print(" mGotFirstWindow="); pw.print(mKeyWaiter.mGotFirstWindow); + pw.print(" mEventDispatching="); pw.print(mKeyWaiter.mEventDispatching); + pw.print(" mTimeToSwitch="); pw.println(mKeyWaiter.mTimeToSwitch); + } } } + // Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection). public void monitor() { synchronized (mWindowMap) { } synchronized (mKeyguardTokenWatcher) { } synchronized (mKeyWaiter) { } + synchronized (mInputMonitor) { } } public void virtualKeyFeedback(KeyEvent event) { diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 1a6119a..0106e6c 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -19,14 +19,17 @@ //#define LOG_NDEBUG 0 // Log debug messages about InputReaderPolicy -#define DEBUG_INPUT_READER_POLICY 1 +#define DEBUG_INPUT_READER_POLICY 0 // Log debug messages about InputDispatcherPolicy -#define DEBUG_INPUT_DISPATCHER_POLICY 1 +#define DEBUG_INPUT_DISPATCHER_POLICY 0 +// Log debug messages about input focus tracking +#define DEBUG_FOCUS 0 #include "JNIHelp.h" #include "jni.h" +#include <limits.h> #include <android_runtime/AndroidRuntime.h> #include <ui/InputReader.h> #include <ui/InputDispatcher.h> @@ -37,10 +40,94 @@ #include "../../core/jni/android_view_KeyEvent.h" #include "../../core/jni/android_view_MotionEvent.h" #include "../../core/jni/android_view_InputChannel.h" -#include "../../core/jni/android_view_InputTarget.h" namespace android { +// Window flags from WindowManager.LayoutParams +enum { + FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001, + FLAG_DIM_BEHIND = 0x00000002, + FLAG_BLUR_BEHIND = 0x00000004, + FLAG_NOT_FOCUSABLE = 0x00000008, + FLAG_NOT_TOUCHABLE = 0x00000010, + FLAG_NOT_TOUCH_MODAL = 0x00000020, + FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040, + FLAG_KEEP_SCREEN_ON = 0x00000080, + FLAG_LAYOUT_IN_SCREEN = 0x00000100, + FLAG_LAYOUT_NO_LIMITS = 0x00000200, + FLAG_FULLSCREEN = 0x00000400, + FLAG_FORCE_NOT_FULLSCREEN = 0x00000800, + FLAG_DITHER = 0x00001000, + FLAG_SECURE = 0x00002000, + FLAG_SCALED = 0x00004000, + FLAG_IGNORE_CHEEK_PRESSES = 0x00008000, + FLAG_LAYOUT_INSET_DECOR = 0x00010000, + FLAG_ALT_FOCUSABLE_IM = 0x00020000, + FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000, + FLAG_SHOW_WHEN_LOCKED = 0x00080000, + FLAG_SHOW_WALLPAPER = 0x00100000, + FLAG_TURN_SCREEN_ON = 0x00200000, + FLAG_DISMISS_KEYGUARD = 0x00400000, + FLAG_IMMERSIVE = 0x00800000, + FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000, + FLAG_COMPATIBLE_WINDOW = 0x20000000, + FLAG_SYSTEM_ERROR = 0x40000000, +}; + +// Window types from WindowManager.LayoutParams +enum { + FIRST_APPLICATION_WINDOW = 1, + TYPE_BASE_APPLICATION = 1, + TYPE_APPLICATION = 2, + TYPE_APPLICATION_STARTING = 3, + LAST_APPLICATION_WINDOW = 99, + FIRST_SUB_WINDOW = 1000, + TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW, + TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1, + TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2, + TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3, + TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4, + LAST_SUB_WINDOW = 1999, + FIRST_SYSTEM_WINDOW = 2000, + TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW, + TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1, + TYPE_PHONE = FIRST_SYSTEM_WINDOW+2, + TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3, + TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4, + TYPE_TOAST = FIRST_SYSTEM_WINDOW+5, + TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6, + TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7, + TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8, + TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9, + TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10, + TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11, + TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12, + TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13, + TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14, + LAST_SYSTEM_WINDOW = 2999, +}; + +enum { + POWER_MANAGER_OTHER_EVENT = 0, + POWER_MANAGER_CHEEK_EVENT = 1, + POWER_MANAGER_TOUCH_EVENT = 2, // touch events are TOUCH for 300ms, and then either + // up events or LONG_TOUCH events. + POWER_MANAGER_LONG_TOUCH_EVENT = 3, + POWER_MANAGER_TOUCH_UP_EVENT = 4, + POWER_MANAGER_BUTTON_EVENT = 5, // Button and trackball events. +}; + +// Delay between reporting long touch events to the power manager. +const nsecs_t EVENT_IGNORE_DURATION = 300 * 1000000LL; // 300 ms + +// Default input dispatching timeout if there is no focused application or paused window +// from which to determine an appropriate dispatching timeout. +const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec + +// Minimum amount of time to provide to the input dispatcher for delivery of an event +// regardless of how long the application window was paused. +const nsecs_t MIN_INPUT_DISPATCHING_TIMEOUT = 1000 * 1000000LL; // 1 sec + // ---------------------------------------------------------------------------- static struct { @@ -53,17 +140,18 @@ static struct { jmethodID notifyInputChannelBroken; jmethodID notifyInputChannelANR; jmethodID notifyInputChannelRecoveredFromANR; + jmethodID notifyANR; jmethodID virtualKeyFeedback; - jmethodID hackInterceptKey; + jmethodID interceptKeyBeforeQueueing; + jmethodID interceptKeyBeforeDispatching; + jmethodID checkInjectEventsPermission; jmethodID goToSleep; - jmethodID pokeUserActivityForKey; + jmethodID pokeUserActivity; jmethodID notifyAppSwitchComing; jmethodID filterTouchEvents; jmethodID filterJumpyTouchEvents; jmethodID getVirtualKeyDefinitions; jmethodID getExcludedDeviceNames; - jmethodID getKeyEventTargets; - jmethodID getMotionEventTargets; } gCallbacksClassInfo; static struct { @@ -79,9 +167,37 @@ static struct { static struct { jclass clazz; - jmethodID ctor; - jfieldID mArray; -} gInputTargetListClassInfo; + jfieldID inputChannel; + jfieldID layoutParamsFlags; + jfieldID layoutParamsType; + jfieldID dispatchingTimeoutNanos; + jfieldID frameLeft; + jfieldID frameTop; + jfieldID touchableAreaLeft; + jfieldID touchableAreaTop; + jfieldID touchableAreaRight; + jfieldID touchableAreaBottom; + jfieldID visible; + jfieldID hasFocus; + jfieldID hasWallpaper; + jfieldID paused; + jfieldID ownerPid; + jfieldID ownerUid; +} gInputWindowClassInfo; + +static struct { + jclass clazz; + + jfieldID name; + jfieldID dispatchingTimeoutNanos; + jfieldID token; +} gInputApplicationClassInfo; + +// ---------------------------------------------------------------------------- + +static inline nsecs_t now() { + return systemTime(SYSTEM_TIME_MONOTONIC); +} // ---------------------------------------------------------------------------- @@ -103,6 +219,11 @@ public: jweak inputChannelObjWeak); status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel); + void setInputWindows(JNIEnv* env, jobjectArray windowObjArray); + void setFocusedApplication(JNIEnv* env, jobject applicationObj); + void setInputDispatchMode(bool enabled, bool frozen); + void preemptInputDispatch(); + /* --- InputReaderPolicyInterface implementation --- */ virtual bool getDisplayInfo(int32_t displayId, @@ -130,16 +251,66 @@ public: nsecs_t& outNewTimeout); virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel); virtual nsecs_t getKeyRepeatTimeout(); - virtual int32_t getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, + virtual int32_t waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets); - virtual int32_t getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, + virtual int32_t waitForMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets); private: + struct InputWindow { + sp<InputChannel> inputChannel; + int32_t layoutParamsFlags; + int32_t layoutParamsType; + nsecs_t dispatchingTimeout; + int32_t frameLeft; + int32_t frameTop; + int32_t touchableAreaLeft; + int32_t touchableAreaTop; + int32_t touchableAreaRight; + int32_t touchableAreaBottom; + bool visible; + bool hasFocus; + bool hasWallpaper; + bool paused; + int32_t ownerPid; + int32_t ownerUid; + + inline bool touchableAreaContainsPoint(int32_t x, int32_t y) { + return x >= touchableAreaLeft && x <= touchableAreaRight + && y >= touchableAreaTop && y <= touchableAreaBottom; + } + }; + + struct InputApplication { + String8 name; + nsecs_t dispatchingTimeout; + jweak tokenObjWeak; + }; + + class ANRTimer { + enum Budget { + SYSTEM = 0, + APPLICATION = 1 + }; + + Budget mBudget; + nsecs_t mStartTime; + bool mFrozen; + InputWindow* mPausedWindow; + + public: + ANRTimer(); + + void dispatchFrozenBySystem(); + void dispatchPausedByApplication(InputWindow* pausedWindow); + bool waitForDispatchStateChangeLd(NativeInputManager* inputManager); + + nsecs_t getTimeSpentWaitingForApplication() const; + }; + sp<InputManager> mInputManager; jobject mCallbacksObj; - jobject mReusableInputTargetListObj; // Cached filtering policies. int32_t mFilterTouchEvents; @@ -160,30 +331,78 @@ private: jobject getInputChannelObjLocal(JNIEnv* env, const sp<InputChannel>& inputChannel); + // Input target and focus tracking. (lock mDispatchLock) + Mutex mDispatchLock; + Condition mDispatchStateChanged; + + bool mDispatchEnabled; + bool mDispatchFrozen; + bool mWindowsReady; + Vector<InputWindow> mWindows; + Vector<InputWindow*> mWallpaperWindows; + + // Focus tracking for keys, trackball, etc. + InputWindow* mFocusedWindow; + + // Focus tracking for touch. + bool mTouchDown; + InputWindow* mTouchedWindow; // primary target for current down + Vector<InputWindow*> mTouchedWallpaperWindows; // wallpaper targets + + Vector<InputWindow*> mTempTouchedOutsideWindows; // temporary outside touch targets + Vector<sp<InputChannel> > mTempTouchedWallpaperChannels; // temporary wallpaper targets + + // Focused application. + InputApplication* mFocusedApplication; + InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication + + void dumpDispatchStateLd(); + + bool notifyANR(jobject tokenObj, nsecs_t& outNewTimeout); + void releaseFocusedApplicationLd(JNIEnv* env); + + int32_t waitForFocusedWindowLd(uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, + Vector<InputTarget>& outTargets, InputWindow*& outFocusedWindow); + int32_t waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags, + int32_t injectorPid, int32_t injectorUid, + Vector<InputTarget>& outTargets, InputWindow*& outTouchedWindow); + + void releaseTouchedWindowLd(); + + int32_t identifyTrackballEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, + int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets); + int32_t identifyTouchEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, + int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets); + + void pokeUserActivityIfNeeded(int32_t windowType, int32_t eventType); + void pokeUserActivity(nsecs_t eventTime, int32_t eventType); + bool checkInjectionPermission(const InputWindow* window, + int32_t injectorPid, int32_t injectorUid); + + static bool populateWindow(JNIEnv* env, jobject windowObj, InputWindow& outWindow); + static void addTarget(const InputWindow* window, int32_t targetFlags, + nsecs_t timeSpentWaitingForApplication, Vector<InputTarget>& outTargets); + static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); } static bool isAppSwitchKey(int32_t keyCode); static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); - static void populateInputTargets(JNIEnv* env, jobject inputTargetListObj, - Vector<InputTarget>& outTargets); }; // ---------------------------------------------------------------------------- NativeInputManager::NativeInputManager(jobject callbacksObj) : mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), - mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(-1) { + mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(-1), + mDispatchEnabled(true), mDispatchFrozen(false), mWindowsReady(true), + mFocusedWindow(NULL), mTouchDown(false), mTouchedWindow(NULL), + mFocusedApplication(NULL) { JNIEnv* env = jniEnv(); mCallbacksObj = env->NewGlobalRef(callbacksObj); - jobject inputTargetListObj = env->NewObject(gInputTargetListClassInfo.clazz, - gInputTargetListClassInfo.ctor); - mReusableInputTargetListObj = env->NewGlobalRef(inputTargetListObj); - env->DeleteLocalRef(inputTargetListObj); - sp<EventHub> eventHub = new EventHub(); mInputManager = new InputManager(eventHub, this, this); } @@ -192,7 +411,8 @@ NativeInputManager::~NativeInputManager() { JNIEnv* env = jniEnv(); env->DeleteGlobalRef(mCallbacksObj); - env->DeleteGlobalRef(mReusableInputTargetListObj); + + releaseFocusedApplicationLd(env); } bool NativeInputManager::isAppSwitchKey(int32_t keyCode) { @@ -362,7 +582,7 @@ int32_t NativeInputManager::interceptKey(nsecs_t when, int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) { #if DEBUG_INPUT_READER_POLICY LOGD("interceptKey - when=%lld, deviceId=%d, down=%d, keyCode=%d, scanCode=%d, " - "policyFlags=%d", + "policyFlags=0x%x", when, deviceId, down, keyCode, scanCode, policyFlags); #endif @@ -375,9 +595,10 @@ int32_t NativeInputManager::interceptKey(nsecs_t when, bool isScreenOn = this->isScreenOn(); bool isScreenBright = this->isScreenBright(); - jint wmActions = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.hackInterceptKey, + jint wmActions = env->CallIntMethod(mCallbacksObj, + gCallbacksClassInfo.interceptKeyBeforeQueueing, deviceId, EV_KEY, scanCode, keyCode, policyFlags, down ? 1 : 0, when, isScreenOn); - if (checkAndClearExceptionFromCallback(env, "hackInterceptKey")) { + if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) { wmActions = 0; } @@ -398,8 +619,7 @@ int32_t NativeInputManager::interceptKey(nsecs_t when, } if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) { - env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.pokeUserActivityForKey, when); - checkAndClearExceptionFromCallback(env, "pokeUserActivityForKey"); + pokeUserActivity(when, POWER_MANAGER_BUTTON_EVENT); } if (wmActions & WM_ACTION_PASS_TO_USER) { @@ -412,6 +632,9 @@ int32_t NativeInputManager::interceptKey(nsecs_t when, actions |= InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING; } } + + // TODO Be smarter about which keys cause us to request interception during dispatch. + actions |= InputReaderPolicyInterface::ACTION_INTERCEPT_DISPATCH; return actions; } @@ -423,12 +646,13 @@ int32_t NativeInputManager::interceptTouch(nsecs_t when) { int32_t actions = InputReaderPolicyInterface::ACTION_NONE; if (isScreenOn()) { // Only dispatch touch events when the device is awake. + // Do not wake the device. actions |= InputReaderPolicyInterface::ACTION_DISPATCH; - } - if (! isScreenBright()) { - // Brighten the screen if dimmed. - actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE; + if (! isScreenBright()) { + // Brighten the screen if dimmed. + actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE; + } } return actions; @@ -444,12 +668,13 @@ int32_t NativeInputManager::interceptTrackball(nsecs_t when, int32_t actions = InputReaderPolicyInterface::ACTION_NONE; if (isScreenOn()) { // Only dispatch trackball events when the device is awake. + // Do not wake the device. actions |= InputReaderPolicyInterface::ACTION_DISPATCH; - } - if (! isScreenBright()) { - // Brighten the screen if dimmed. - actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE; + if (! isScreenBright()) { + // Brighten the screen if dimmed. + actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE; + } } return actions; @@ -639,6 +864,27 @@ void NativeInputManager::notifyInputChannelRecoveredFromANR(const sp<InputChanne } } +bool NativeInputManager::notifyANR(jobject tokenObj, nsecs_t& outNewTimeout) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("notifyANR"); +#endif + + JNIEnv* env = jniEnv(); + + jlong newTimeout = env->CallLongMethod(mCallbacksObj, + gCallbacksClassInfo.notifyANR, tokenObj); + if (checkAndClearExceptionFromCallback(env, "notifyANR")) { + newTimeout = -2; + } + + if (newTimeout == -2) { + return false; // abort + } + + outNewTimeout = newTimeout; + return true; // resume +} + nsecs_t NativeInputManager::getKeyRepeatTimeout() { if (! isScreenOn()) { // Disable key repeat when the screen is off. @@ -649,85 +895,898 @@ nsecs_t NativeInputManager::getKeyRepeatTimeout() { } } -int32_t NativeInputManager::getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, - int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) { -#if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("getKeyEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d", - policyFlags, injectorPid, injectorUid); +void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArray) { +#if DEBUG_FOCUS + LOGD("setInputWindows"); #endif + { // acquire lock + AutoMutex _l(mDispatchLock); - JNIEnv* env = jniEnv(); + sp<InputChannel> touchedWindowChannel; + if (mTouchedWindow) { + touchedWindowChannel = mTouchedWindow->inputChannel; + mTouchedWindow = NULL; + } + size_t numTouchedWallpapers = mTouchedWallpaperWindows.size(); + if (numTouchedWallpapers != 0) { + for (size_t i = 0; i < numTouchedWallpapers; i++) { + mTempTouchedWallpaperChannels.push(mTouchedWallpaperWindows[i]->inputChannel); + } + mTouchedWallpaperWindows.clear(); + } + + mWindows.clear(); + mFocusedWindow = NULL; + mWallpaperWindows.clear(); + + if (windowObjArray) { + mWindowsReady = true; + + jsize length = env->GetArrayLength(windowObjArray); + for (jsize i = 0; i < length; i++) { + jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i); + if (! inputTargetObj) { + break; // found null element indicating end of used portion of the array + } + + mWindows.push(); + InputWindow& window = mWindows.editTop(); + bool valid = populateWindow(env, inputTargetObj, window); + if (! valid) { + mWindows.pop(); + } + + env->DeleteLocalRef(inputTargetObj); + } + + size_t numWindows = mWindows.size(); + for (size_t i = 0; i < numWindows; i++) { + InputWindow* window = & mWindows.editItemAt(i); + if (window->hasFocus) { + mFocusedWindow = window; + } + + if (window->layoutParamsType == TYPE_WALLPAPER) { + mWallpaperWindows.push(window); + + for (size_t j = 0; j < numTouchedWallpapers; j++) { + if (window->inputChannel == mTempTouchedWallpaperChannels[i]) { + mTouchedWallpaperWindows.push(window); + } + } + } + + if (window->inputChannel == touchedWindowChannel) { + mTouchedWindow = window; + } + } + } else { + mWindowsReady = false; + } + + mTempTouchedWallpaperChannels.clear(); + + mDispatchStateChanged.broadcast(); + +#if DEBUG_FOCUS + dumpDispatchStateLd(); +#endif + } // release lock +} + +bool NativeInputManager::populateWindow(JNIEnv* env, jobject windowObj, + InputWindow& outWindow) { + bool valid = false; + + jobject inputChannelObj = env->GetObjectField(windowObj, + gInputWindowClassInfo.inputChannel); + if (inputChannelObj) { + sp<InputChannel> inputChannel = + android_view_InputChannel_getInputChannel(env, inputChannelObj); + if (inputChannel != NULL) { + jint layoutParamsFlags = env->GetIntField(windowObj, + gInputWindowClassInfo.layoutParamsFlags); + jint layoutParamsType = env->GetIntField(windowObj, + gInputWindowClassInfo.layoutParamsType); + jlong dispatchingTimeoutNanos = env->GetLongField(windowObj, + gInputWindowClassInfo.dispatchingTimeoutNanos); + jint frameLeft = env->GetIntField(windowObj, + gInputWindowClassInfo.frameLeft); + jint frameTop = env->GetIntField(windowObj, + gInputWindowClassInfo.frameTop); + jint touchableAreaLeft = env->GetIntField(windowObj, + gInputWindowClassInfo.touchableAreaLeft); + jint touchableAreaTop = env->GetIntField(windowObj, + gInputWindowClassInfo.touchableAreaTop); + jint touchableAreaRight = env->GetIntField(windowObj, + gInputWindowClassInfo.touchableAreaRight); + jint touchableAreaBottom = env->GetIntField(windowObj, + gInputWindowClassInfo.touchableAreaBottom); + jboolean visible = env->GetBooleanField(windowObj, + gInputWindowClassInfo.visible); + jboolean hasFocus = env->GetBooleanField(windowObj, + gInputWindowClassInfo.hasFocus); + jboolean hasWallpaper = env->GetBooleanField(windowObj, + gInputWindowClassInfo.hasWallpaper); + jboolean paused = env->GetBooleanField(windowObj, + gInputWindowClassInfo.paused); + jint ownerPid = env->GetIntField(windowObj, + gInputWindowClassInfo.ownerPid); + jint ownerUid = env->GetIntField(windowObj, + gInputWindowClassInfo.ownerUid); + + outWindow.inputChannel = inputChannel; + outWindow.layoutParamsFlags = layoutParamsFlags; + outWindow.layoutParamsType = layoutParamsType; + outWindow.dispatchingTimeout = dispatchingTimeoutNanos; + outWindow.frameLeft = frameLeft; + outWindow.frameTop = frameTop; + outWindow.touchableAreaLeft = touchableAreaLeft; + outWindow.touchableAreaTop = touchableAreaTop; + outWindow.touchableAreaRight = touchableAreaRight; + outWindow.touchableAreaBottom = touchableAreaBottom; + outWindow.visible = visible; + outWindow.hasFocus = hasFocus; + outWindow.hasWallpaper = hasWallpaper; + outWindow.paused = paused; + outWindow.ownerPid = ownerPid; + outWindow.ownerUid = ownerUid; + valid = true; + } else { + LOGW("Dropping input target because its input channel is not initialized."); + } - jint injectionResult; - jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent); - if (! keyEventObj) { - LOGE("Could not obtain DVM KeyEvent object to get key event targets."); - injectionResult = INPUT_EVENT_INJECTION_FAILED; + env->DeleteLocalRef(inputChannelObj); } else { - jint injectionResult = env->CallIntMethod(mCallbacksObj, - gCallbacksClassInfo.getKeyEventTargets, mReusableInputTargetListObj, - keyEventObj, jint(keyEvent->getNature()), jint(policyFlags), - jint(injectorPid), jint(injectorUid)); - if (checkAndClearExceptionFromCallback(env, "getKeyEventTargets")) { + LOGW("Dropping input target because the input channel object was null."); + } + return valid; +} + +void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationObj) { +#if DEBUG_FOCUS + LOGD("setFocusedApplication"); +#endif + { // acquire lock + AutoMutex _l(mDispatchLock); + + releaseFocusedApplicationLd(env); + + if (applicationObj) { + jstring nameObj = jstring(env->GetObjectField(applicationObj, + gInputApplicationClassInfo.name)); + jlong dispatchingTimeoutNanos = env->GetLongField(applicationObj, + gInputApplicationClassInfo.dispatchingTimeoutNanos); + jobject tokenObj = env->GetObjectField(applicationObj, + gInputApplicationClassInfo.token); + jweak tokenObjWeak = env->NewWeakGlobalRef(tokenObj); + if (! tokenObjWeak) { + LOGE("Could not create weak reference for application token."); + LOGE_EX(env); + env->ExceptionClear(); + } + env->DeleteLocalRef(tokenObj); + + mFocusedApplication = & mFocusedApplicationStorage; + + if (nameObj) { + const char* nameStr = env->GetStringUTFChars(nameObj, NULL); + mFocusedApplication->name.setTo(nameStr); + env->ReleaseStringUTFChars(nameObj, nameStr); + env->DeleteLocalRef(nameObj); + } else { + LOGE("InputApplication.name should not be null."); + mFocusedApplication->name.setTo("unknown"); + } + + mFocusedApplication->dispatchingTimeout = dispatchingTimeoutNanos; + mFocusedApplication->tokenObjWeak = tokenObjWeak; + } + + mDispatchStateChanged.broadcast(); + +#if DEBUG_FOCUS + dumpDispatchStateLd(); +#endif + } // release lock +} + +void NativeInputManager::releaseFocusedApplicationLd(JNIEnv* env) { + if (mFocusedApplication) { + env->DeleteWeakGlobalRef(mFocusedApplication->tokenObjWeak); + mFocusedApplication = NULL; + } +} + +void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) { +#if DEBUG_FOCUS + LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen); +#endif + + { // acquire lock + AutoMutex _l(mDispatchLock); + + if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) { + mDispatchEnabled = enabled; + mDispatchFrozen = frozen; + + mDispatchStateChanged.broadcast(); + } + +#if DEBUG_FOCUS + dumpDispatchStateLd(); +#endif + } // release lock +} + +void NativeInputManager::preemptInputDispatch() { +#if DEBUG_FOCUS + LOGD("preemptInputDispatch"); +#endif + + mInputManager->preemptInputDispatch(); +} + +int32_t NativeInputManager::waitForFocusedWindowLd(uint32_t policyFlags, + int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets, + InputWindow*& outFocusedWindow) { + + int32_t injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + bool firstIteration = true; + ANRTimer anrTimer; + for (;;) { + if (firstIteration) { + firstIteration = false; + } else { + if (! anrTimer.waitForDispatchStateChangeLd(this)) { + LOGW("Dropping event because the dispatcher timed out waiting to identify " + "the window that should receive it."); + injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; + break; + } + } + + // If dispatch is not enabled then fail. + if (! mDispatchEnabled) { + LOGI("Dropping event because input dispatch is disabled."); injectionResult = INPUT_EVENT_INJECTION_FAILED; + break; + } + + // If dispatch is frozen or we don't have valid window data yet then wait. + if (mDispatchFrozen || ! mWindowsReady) { +#if DEBUG_FOCUS + LOGD("Waiting because dispatch is frozen or windows are not ready."); +#endif + anrTimer.dispatchFrozenBySystem(); + continue; + } + + // If there is no currently focused window and no focused application + // then drop the event. + if (! mFocusedWindow) { + if (mFocusedApplication) { +#if DEBUG_FOCUS + LOGD("Waiting because there is no focused window but there is a " + "focused application that may yet introduce a new target: '%s'.", + mFocusedApplication->name.string()); +#endif + continue; + } + + LOGI("Dropping event because there is no focused window or focused application."); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + break; + } + + // Check permissions. + if (! checkInjectionPermission(mFocusedWindow, injectorPid, injectorUid)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + break; + } + + // If the currently focused window is paused then keep waiting. + if (mFocusedWindow->paused) { +#if DEBUG_FOCUS + LOGD("Waiting because focused window is paused."); +#endif + anrTimer.dispatchPausedByApplication(mFocusedWindow); + continue; + } + + // Success! + break; // done waiting, exit loop + } + + // Output targets. + if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) { + addTarget(mFocusedWindow, InputTarget::FLAG_SYNC, + anrTimer.getTimeSpentWaitingForApplication(), outTargets); + + outFocusedWindow = mFocusedWindow; + } else { + outFocusedWindow = NULL; + } + +#if DEBUG_FOCUS + LOGD("waitForFocusedWindow finished: injectionResult=%d", + injectionResult); + dumpDispatchStateLd(); +#endif + return injectionResult; +} + +int32_t NativeInputManager::waitForTouchedWindowLd(MotionEvent* motionEvent, uint32_t policyFlags, + int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets, + InputWindow*& outTouchedWindow) { + nsecs_t startTime = now(); + + int32_t injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + int32_t action = motionEvent->getAction(); + + // For security reasons, we defer updating the touch state until we are sure that + // event injection will be allowed. + // + // FIXME In the original code, screenWasOff could never be set to true. + // The reason is that the POLICY_FLAG_WOKE_HERE + // and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw + // EV_KEY, EV_REL and EV_ABS events. As it happens, the touch event was + // actually enqueued using the policyFlags that appeared in the final EV_SYN + // events upon which no preprocessing took place. So policyFlags was always 0. + // In the new native input dispatcher we're a bit more careful about event + // preprocessing so the touches we receive can actually have non-zero policyFlags. + // Unfortunately we obtain undesirable behavior. + // + // Here's what happens: + // + // When the device dims in anticipation of going to sleep, touches + // in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause + // the device to brighten and reset the user activity timer. + // Touches on other windows (such as the launcher window) + // are dropped. Then after a moment, the device goes to sleep. Oops. + // + // Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE + // instead of POLICY_FLAG_WOKE_HERE... + // + bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE; + bool firstIteration = true; + ANRTimer anrTimer; + for (;;) { + if (firstIteration) { + firstIteration = false; + } else { + if (! anrTimer.waitForDispatchStateChangeLd(this)) { + LOGW("Dropping event because the dispatcher timed out waiting to identify " + "the window that should receive it."); + injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; + break; + } + } + + // If dispatch is not enabled then fail. + if (! mDispatchEnabled) { + LOGI("Dropping event because input dispatch is disabled."); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + break; // failed, exit wait loop + } + + // If dispatch is frozen or we don't have valid window data yet then wait. + if (mDispatchFrozen || ! mWindowsReady) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Waiting because dispatch is frozen or windows are not ready."); +#endif + anrTimer.dispatchFrozenBySystem(); + continue; + } + + // Update the touch state as needed based on the properties of the touch event. + if (action == MOTION_EVENT_ACTION_DOWN) { + InputWindow* newTouchedWindow = NULL; + mTempTouchedOutsideWindows.clear(); + + int32_t x = int32_t(motionEvent->getX(0)); + int32_t y = int32_t(motionEvent->getY(0)); + InputWindow* topErrorWindow = NULL; + + // Traverse windows from front to back to find touched window and outside targets. + size_t numWindows = mWindows.size(); + for (size_t i = 0; i < numWindows; i++) { + InputWindow* window = & mWindows.editItemAt(i); + int32_t flags = window->layoutParamsFlags; + + if (flags & FLAG_SYSTEM_ERROR) { + if (! topErrorWindow) { + topErrorWindow = window; + } + } + + if (window->visible) { + if (! (flags & FLAG_NOT_TOUCHABLE)) { + bool isTouchModal = (flags & + (FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL)) == 0; + if (isTouchModal || window->touchableAreaContainsPoint(x, y)) { + if (! screenWasOff || flags & FLAG_TOUCHABLE_WHEN_WAKING) { + newTouchedWindow = window; + } + break; // found touched window, exit window loop + } + } + + if (flags & FLAG_WATCH_OUTSIDE_TOUCH) { + mTempTouchedOutsideWindows.push(window); + } + } + } + + // If there is an error window but it is not taking focus (typically because + // it is invisible) then wait for it. Any other focused window may in + // fact be in ANR state. + if (topErrorWindow && newTouchedWindow != topErrorWindow) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Waiting because system error window is pending."); +#endif + anrTimer.dispatchFrozenBySystem(); + continue; // wait some more + } + + // If we did not find a touched window then fail. + if (! newTouchedWindow) { + if (mFocusedApplication) { +#if DEBUG_FOCUS + LOGD("Waiting because there is no focused window but there is a " + "focused application that may yet introduce a new target: '%s'.", + mFocusedApplication->name.string()); +#endif + continue; + } + + LOGI("Dropping event because there is no touched window or focused application."); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + break; // failed, exit wait loop + } + + // Check permissions. + if (! checkInjectionPermission(newTouchedWindow, injectorPid, injectorUid)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + break; // failed, exit wait loop + } + + // If the touched window is paused then keep waiting. + if (newTouchedWindow->paused) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Waiting because touched window is paused."); +#endif + anrTimer.dispatchPausedByApplication(newTouchedWindow); + continue; // wait some more + } + + // Success! Update the touch dispatch state for real. + releaseTouchedWindowLd(); + + mTouchedWindow = newTouchedWindow; + + if (newTouchedWindow->hasWallpaper) { + mTouchedWallpaperWindows.appendVector(mWallpaperWindows); + } + break; // done } else { - populateInputTargets(env, mReusableInputTargetListObj, outTargets); + // Check permissions. + if (! checkInjectionPermission(mTouchedWindow, injectorPid, injectorUid)) { + injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; + break; // failed, exit wait loop + } + + // If there is no currently touched window then fail. + if (! mTouchedWindow || ! mTouchDown) { + LOGI("Dropping event because touched window is no longer valid."); + injectionResult = INPUT_EVENT_INJECTION_FAILED; + break; // failed, exit wait loop + } + + // If the touched window is paused then keep waiting. + if (mTouchedWindow->paused) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("Waiting because touched window is paused."); +#endif + anrTimer.dispatchPausedByApplication(mTouchedWindow); + continue; // wait some more + } + + // Success! + break; // done } - env->DeleteLocalRef(keyEventObj); } + + // Output targets. + bool havePermission; + if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) { + // Injection succeeded so the injector must have permission. + havePermission = true; + + size_t numWallpaperWindows = mTouchedWallpaperWindows.size(); + for (size_t i = 0; i < numWallpaperWindows; i++) { + addTarget(mTouchedWallpaperWindows[i], 0, 0, outTargets); + } + + size_t numOutsideWindows = mTempTouchedOutsideWindows.size(); + for (size_t i = 0; i < numOutsideWindows; i++) { + addTarget(mTempTouchedOutsideWindows[i], InputTarget::FLAG_OUTSIDE, 0, outTargets); + } + + addTarget(mTouchedWindow, InputTarget::FLAG_SYNC, + anrTimer.getTimeSpentWaitingForApplication(), outTargets); + outTouchedWindow = mTouchedWindow; + } else { + if (injectionResult != INPUT_EVENT_INJECTION_PERMISSION_DENIED + && checkInjectionPermission(NULL, injectorPid, injectorUid)) { + // Injection failed but the injector does have permission to inject events. + // While we might not have found a valid target for the injected event, we + // still want to update the dispatch state to take it into account. + havePermission = true; + } else { + // Injector does not have permission to inject events. + // We make sure to leave the dispatch state unchanged. + havePermission = false; + } + outTouchedWindow = NULL; + } + mTempTouchedOutsideWindows.clear(); + + // Update final pieces of touch state now that we know for sure whether the injector + // had permission to perform the injection. + if (havePermission) { + if (action == MOTION_EVENT_ACTION_DOWN) { + if (mTouchDown) { + // This is weird. We got a down but we thought it was already down! + LOGW("Pointer down received while already down."); + } else { + mTouchDown = true; + } + + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + // Since we failed to identify a target for this touch down, we may still + // be holding on to an earlier target from a previous touch down. Release it. + releaseTouchedWindowLd(); + } + } else if (action == MOTION_EVENT_ACTION_UP) { + mTouchDown = false; + releaseTouchedWindowLd(); + } + } + +#if DEBUG_FOCUS + LOGD("waitForTouchedWindow finished: injectionResult=%d", + injectionResult); + dumpDispatchStateLd(); +#endif return injectionResult; } -int32_t NativeInputManager::getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, +void NativeInputManager::releaseTouchedWindowLd() { + mTouchedWindow = NULL; + mTouchedWallpaperWindows.clear(); +} + +void NativeInputManager::addTarget(const InputWindow* window, int32_t targetFlags, + nsecs_t timeSpentWaitingForApplication, Vector<InputTarget>& outTargets) { + nsecs_t timeout = window->dispatchingTimeout - timeSpentWaitingForApplication; + if (timeout < MIN_INPUT_DISPATCHING_TIMEOUT) { + timeout = MIN_INPUT_DISPATCHING_TIMEOUT; + } + + outTargets.push(); + + InputTarget& target = outTargets.editTop(); + target.inputChannel = window->inputChannel; + target.flags = targetFlags; + target.timeout = timeout; + target.xOffset = - window->frameLeft; + target.yOffset = - window->frameTop; +} + +bool NativeInputManager::checkInjectionPermission(const InputWindow* window, + int32_t injectorPid, int32_t injectorUid) { + if (injectorUid > 0 && (window == NULL || window->ownerUid != injectorUid)) { + JNIEnv* env = jniEnv(); + jboolean result = env->CallBooleanMethod(mCallbacksObj, + gCallbacksClassInfo.checkInjectEventsPermission, injectorPid, injectorUid); + checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission"); + + if (! result) { + if (window) { + LOGW("Permission denied: injecting event from pid %d uid %d to window " + "with input channel %s owned by uid %d", + injectorPid, injectorUid, window->inputChannel->getName().string(), + window->ownerUid); + } else { + LOGW("Permission denied: injecting event from pid %d uid %d", + injectorPid, injectorUid); + } + return false; + } + } + + return true; +} + +int32_t NativeInputManager::waitForKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) { #if DEBUG_INPUT_DISPATCHER_POLICY - LOGD("getMotionEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d", + LOGD("waitForKeyEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d", policyFlags, injectorPid, injectorUid); #endif - JNIEnv* env = jniEnv(); + int32_t windowType; + { // acquire lock + AutoMutex _l(mDispatchLock); - jint injectionResult; - jobject motionEventObj = android_view_MotionEvent_fromNative(env, motionEvent); - if (! motionEventObj) { - LOGE("Could not obtain DVM MotionEvent object to get key event targets."); - injectionResult = INPUT_EVENT_INJECTION_FAILED; - } else { - jint injectionResult = env->CallIntMethod(mCallbacksObj, - gCallbacksClassInfo.getMotionEventTargets, mReusableInputTargetListObj, - motionEventObj, jint(motionEvent->getNature()), jint(policyFlags), - jint(injectorPid), jint(injectorUid)); - if (checkAndClearExceptionFromCallback(env, "getMotionEventTargets")) { - injectionResult = INPUT_EVENT_INJECTION_FAILED; + InputWindow* focusedWindow; + int32_t injectionResult = waitForFocusedWindowLd(policyFlags, + injectorPid, injectorUid, outTargets, /*out*/ focusedWindow); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return injectionResult; + } + + windowType = focusedWindow->layoutParamsType; + } // release lock + + if (policyFlags & POLICY_FLAG_INTERCEPT_DISPATCH) { + const InputTarget& target = outTargets.top(); + + JNIEnv* env = jniEnv(); + + jobject inputChannelObj = getInputChannelObjLocal(env, target.inputChannel); + if (inputChannelObj) { + jboolean consumed = env->CallBooleanMethod(mCallbacksObj, + gCallbacksClassInfo.interceptKeyBeforeDispatching, + inputChannelObj, keyEvent->getKeyCode(), keyEvent->getMetaState(), + keyEvent->getAction() == KEY_EVENT_ACTION_DOWN, + keyEvent->getRepeatCount(), policyFlags); + bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatch"); + + env->DeleteLocalRef(inputChannelObj); + + if (error) { + return INPUT_EVENT_INJECTION_FAILED; + } + + if (consumed) { + outTargets.clear(); + return INPUT_EVENT_INJECTION_SUCCEEDED; + } } else { - populateInputTargets(env, mReusableInputTargetListObj, outTargets); + LOGW("Could not apply key dispatch policy because input channel '%s' is " + "no longer valid.", target.inputChannel->getName().string()); } - android_view_MotionEvent_recycle(env, motionEventObj); - env->DeleteLocalRef(motionEventObj); } - return injectionResult; + + pokeUserActivityIfNeeded(windowType, POWER_MANAGER_BUTTON_EVENT); + return INPUT_EVENT_INJECTION_SUCCEEDED; +} + +int32_t NativeInputManager::waitForMotionEventTargets(MotionEvent* motionEvent, + uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, + Vector<InputTarget>& outTargets) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("waitForMotionEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d", + policyFlags, injectorPid, injectorUid); +#endif + + switch (motionEvent->getNature()) { + case INPUT_EVENT_NATURE_TRACKBALL: + return identifyTrackballEventTargets(motionEvent, policyFlags, injectorPid, injectorUid, + outTargets); + + case INPUT_EVENT_NATURE_TOUCH: + return identifyTouchEventTargets(motionEvent, policyFlags, injectorPid, injectorUid, + outTargets); + + default: + assert(false); + return INPUT_EVENT_INJECTION_FAILED; + } +} + +int32_t NativeInputManager::identifyTrackballEventTargets(MotionEvent* motionEvent, + uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, + Vector<InputTarget>& outTargets) { +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("identifyTrackballEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d", + policyFlags, injectorPid, injectorUid); +#endif + + int32_t windowType; + { // acquire lock + AutoMutex _l(mDispatchLock); + + InputWindow* focusedWindow; + int32_t injectionResult = waitForFocusedWindowLd(policyFlags, + injectorPid, injectorUid, outTargets, /*out*/ focusedWindow); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return injectionResult; + } + + windowType = focusedWindow->layoutParamsType; + } // release lock + + pokeUserActivityIfNeeded(windowType, POWER_MANAGER_BUTTON_EVENT); + return INPUT_EVENT_INJECTION_SUCCEEDED; } -void NativeInputManager::populateInputTargets(JNIEnv* env, jobject inputTargetListObj, +int32_t NativeInputManager::identifyTouchEventTargets(MotionEvent* motionEvent, + uint32_t policyFlags, int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) { - jobjectArray inputTargetArray = jobjectArray(env->GetObjectField( - inputTargetListObj, gInputTargetListClassInfo.mArray)); - - jsize length = env->GetArrayLength(inputTargetArray); - for (jsize i = 0; i < length; i++) { - jobject item = env->GetObjectArrayElement(inputTargetArray, i); - if (! item) { - break; // found null element indicating end of used portion of the array +#if DEBUG_INPUT_DISPATCHER_POLICY + LOGD("identifyTouchEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d", + policyFlags, injectorPid, injectorUid); +#endif + + int32_t windowType; + { // acquire lock + AutoMutex _l(mDispatchLock); + + InputWindow* touchedWindow; + int32_t injectionResult = waitForTouchedWindowLd(motionEvent, policyFlags, + injectorPid, injectorUid, outTargets, /*out*/ touchedWindow); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return injectionResult; + } + + windowType = touchedWindow->layoutParamsType; + } // release lock + + int32_t eventType; + switch (motionEvent->getAction()) { + case MOTION_EVENT_ACTION_DOWN: + eventType = POWER_MANAGER_TOUCH_EVENT; + break; + case MOTION_EVENT_ACTION_UP: + eventType = POWER_MANAGER_TOUCH_UP_EVENT; + break; + default: + if (motionEvent->getEventTime() - motionEvent->getDownTime() + >= EVENT_IGNORE_DURATION) { + eventType = POWER_MANAGER_TOUCH_EVENT; + } else { + eventType = POWER_MANAGER_LONG_TOUCH_EVENT; } + break; + } + pokeUserActivityIfNeeded(windowType, eventType); + return INPUT_EVENT_INJECTION_SUCCEEDED; +} + +void NativeInputManager::pokeUserActivityIfNeeded(int32_t windowType, int32_t eventType) { + if (windowType != TYPE_KEYGUARD) { + nsecs_t eventTime = now(); + pokeUserActivity(eventTime, eventType); + } +} - outTargets.add(); - android_view_InputTarget_toNative(env, item, & outTargets.editTop()); +void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) { + JNIEnv* env = jniEnv(); + env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.pokeUserActivity, + eventTime, eventType); + checkAndClearExceptionFromCallback(env, "pokeUserActivity"); +} - env->DeleteLocalRef(item); +void NativeInputManager::dumpDispatchStateLd() { +#if DEBUG_FOCUS + LOGD(" dispatcherState: dispatchEnabled=%d, dispatchFrozen=%d, windowsReady=%d", + mDispatchEnabled, mDispatchFrozen, mWindowsReady); + if (mFocusedApplication) { + LOGD(" focusedApplication: name='%s', dispatchingTimeout=%0.3fms", + mFocusedApplication->name.string(), + mFocusedApplication->dispatchingTimeout / 1000000.0); + } else { + LOGD(" focusedApplication: <null>"); + } + LOGD(" focusedWindow: '%s'", + mFocusedWindow != NULL ? mFocusedWindow->inputChannel->getName().string() : "<null>"); + LOGD(" touchedWindow: '%s', touchDown=%d", + mTouchedWindow != NULL ? mTouchedWindow->inputChannel->getName().string() : "<null>", + mTouchDown); + for (size_t i = 0; i < mTouchedWallpaperWindows.size(); i++) { + LOGD(" touchedWallpaperWindows[%d]: '%s'", + i, mTouchedWallpaperWindows[i]->inputChannel->getName().string()); } - env->DeleteLocalRef(inputTargetArray); + for (size_t i = 0; i < mWindows.size(); i++) { + LOGD(" windows[%d]: '%s', paused=%d, hasFocus=%d, hasWallpaper=%d, visible=%d, " + "flags=0x%08x, type=0x%08x, " + "frame=[%d,%d], touchableArea=[%d,%d][%d,%d], " + "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms", + i, mWindows[i].inputChannel->getName().string(), + mWindows[i].paused, mWindows[i].hasFocus, mWindows[i].hasWallpaper, + mWindows[i].visible, mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType, + mWindows[i].frameLeft, mWindows[i].frameTop, + mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop, + mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom, + mWindows[i].ownerPid, mWindows[i].ownerUid, + mWindows[i].dispatchingTimeout / 1000000.0); + } +#endif } +// ---------------------------------------------------------------------------- + +NativeInputManager::ANRTimer::ANRTimer() : + mBudget(APPLICATION), mStartTime(now()), mFrozen(false), mPausedWindow(NULL) { +} + +void NativeInputManager::ANRTimer::dispatchFrozenBySystem() { + mFrozen = true; +} + +void NativeInputManager::ANRTimer::dispatchPausedByApplication(InputWindow* pausedWindow) { + mPausedWindow = pausedWindow; +} + +bool NativeInputManager::ANRTimer::waitForDispatchStateChangeLd(NativeInputManager* inputManager) { + nsecs_t currentTime = now(); + + Budget newBudget; + nsecs_t dispatchingTimeout; + sp<InputChannel> pausedChannel = NULL; + jobject tokenObj = NULL; + if (mFrozen) { + newBudget = SYSTEM; + dispatchingTimeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; + mFrozen = false; + } else if (mPausedWindow) { + newBudget = APPLICATION; + dispatchingTimeout = mPausedWindow->dispatchingTimeout; + pausedChannel = mPausedWindow->inputChannel; + mPausedWindow = NULL; + } else if (inputManager->mFocusedApplication) { + newBudget = APPLICATION; + dispatchingTimeout = inputManager->mFocusedApplication->dispatchingTimeout; + tokenObj = jniEnv()->NewLocalRef(inputManager->mFocusedApplication->tokenObjWeak); + } else { + newBudget = APPLICATION; + dispatchingTimeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; + } + + if (mBudget != newBudget) { + mBudget = newBudget; + mStartTime = currentTime; + } + + bool result = false; + nsecs_t timeoutRemaining = mStartTime + dispatchingTimeout - currentTime; + if (timeoutRemaining > 0 + && inputManager->mDispatchStateChanged.waitRelative(inputManager->mDispatchLock, + timeoutRemaining) == OK) { + result = true; + } else { + if (pausedChannel != NULL || tokenObj != NULL) { + bool resumed; + nsecs_t newTimeout = 0; + + inputManager->mDispatchLock.unlock(); // release lock + if (pausedChannel != NULL) { + resumed = inputManager->notifyInputChannelANR(pausedChannel, /*out*/ newTimeout); + } else { + resumed = inputManager->notifyANR(tokenObj, /*out*/ newTimeout); + } + inputManager->mDispatchLock.lock(); // re-acquire lock + + if (resumed) { + mStartTime = now() - dispatchingTimeout + newTimeout; + result = true; + } + } + } + + if (tokenObj) { + jniEnv()->DeleteLocalRef(tokenObj); + } + + return result; +} + +nsecs_t NativeInputManager::ANRTimer::getTimeSpentWaitingForApplication() const { + return mBudget == APPLICATION ? now() - mStartTime : 0; +} // ---------------------------------------------------------------------------- @@ -926,6 +1985,42 @@ static jint android_server_InputManager_nativeInjectMotionEvent(JNIEnv* env, jcl injectorPid, injectorUid, sync, timeoutMillis); } +static void android_server_InputManager_nativeSetInputWindows(JNIEnv* env, jclass clazz, + jobjectArray windowObjArray) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->setInputWindows(env, windowObjArray); +} + +static void android_server_InputManager_nativeSetFocusedApplication(JNIEnv* env, jclass clazz, + jobject applicationObj) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->setFocusedApplication(env, applicationObj); +} + +static void android_server_InputManager_nativeSetInputDispatchMode(JNIEnv* env, + jclass clazz, jboolean enabled, jboolean frozen) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->setInputDispatchMode(enabled, frozen); +} + +static void android_server_InputManager_nativePreemptInputDispatch(JNIEnv* env, + jclass clazz) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->preemptInputDispatch(); +} + // ---------------------------------------------------------------------------- static JNINativeMethod gInputManagerMethods[] = { @@ -953,7 +2048,15 @@ static JNINativeMethod gInputManagerMethods[] = { { "nativeInjectKeyEvent", "(Landroid/view/KeyEvent;IIIZI)I", (void*) android_server_InputManager_nativeInjectKeyEvent }, { "nativeInjectMotionEvent", "(Landroid/view/MotionEvent;IIIZI)I", - (void*) android_server_InputManager_nativeInjectMotionEvent } + (void*) android_server_InputManager_nativeInjectMotionEvent }, + { "nativeSetInputWindows", "([Lcom/android/server/InputWindow;)V", + (void*) android_server_InputManager_nativeSetInputWindows }, + { "nativeSetFocusedApplication", "(Lcom/android/server/InputApplication;)V", + (void*) android_server_InputManager_nativeSetFocusedApplication }, + { "nativeSetInputDispatchMode", "(ZZ)V", + (void*) android_server_InputManager_nativeSetInputDispatchMode }, + { "nativePreemptInputDispatch", "()V", + (void*) android_server_InputManager_nativePreemptInputDispatch } }; #define FIND_CLASS(var, className) \ @@ -999,17 +2102,26 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelRecoveredFromANR, gCallbacksClassInfo.clazz, "notifyInputChannelRecoveredFromANR", "(Landroid/view/InputChannel;)V"); + GET_METHOD_ID(gCallbacksClassInfo.notifyANR, gCallbacksClassInfo.clazz, + "notifyANR", "(Ljava/lang/Object;)J"); + GET_METHOD_ID(gCallbacksClassInfo.virtualKeyFeedback, gCallbacksClassInfo.clazz, "virtualKeyFeedback", "(JIIIIIIJ)V"); - GET_METHOD_ID(gCallbacksClassInfo.hackInterceptKey, gCallbacksClassInfo.clazz, - "hackInterceptKey", "(IIIIIIJZ)I"); + GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, gCallbacksClassInfo.clazz, + "interceptKeyBeforeQueueing", "(IIIIIIJZ)I"); + + GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, gCallbacksClassInfo.clazz, + "interceptKeyBeforeDispatching", "(Landroid/view/InputChannel;IIZII)Z"); + + GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, gCallbacksClassInfo.clazz, + "checkInjectEventsPermission", "(II)Z"); GET_METHOD_ID(gCallbacksClassInfo.goToSleep, gCallbacksClassInfo.clazz, "goToSleep", "(J)V"); - GET_METHOD_ID(gCallbacksClassInfo.pokeUserActivityForKey, gCallbacksClassInfo.clazz, - "pokeUserActivityForKey", "(J)V"); + GET_METHOD_ID(gCallbacksClassInfo.pokeUserActivity, gCallbacksClassInfo.clazz, + "pokeUserActivity", "(JI)V"); GET_METHOD_ID(gCallbacksClassInfo.notifyAppSwitchComing, gCallbacksClassInfo.clazz, "notifyAppSwitchComing", "()V"); @@ -1027,14 +2139,6 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz, "getExcludedDeviceNames", "()[Ljava/lang/String;"); - GET_METHOD_ID(gCallbacksClassInfo.getKeyEventTargets, gCallbacksClassInfo.clazz, - "getKeyEventTargets", - "(Lcom/android/server/InputTargetList;Landroid/view/KeyEvent;IIII)I"); - - GET_METHOD_ID(gCallbacksClassInfo.getMotionEventTargets, gCallbacksClassInfo.clazz, - "getMotionEventTargets", - "(Lcom/android/server/InputTargetList;Landroid/view/MotionEvent;IIII)I"); - // VirtualKeyDefinition FIND_CLASS(gVirtualKeyDefinitionClassInfo.clazz, @@ -1055,15 +2159,71 @@ int register_android_server_InputManager(JNIEnv* env) { GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.height, gVirtualKeyDefinitionClassInfo.clazz, "height", "I"); - // InputTargetList + // InputWindow + + FIND_CLASS(gInputWindowClassInfo.clazz, "com/android/server/InputWindow"); + + GET_FIELD_ID(gInputWindowClassInfo.inputChannel, gInputWindowClassInfo.clazz, + "inputChannel", "Landroid/view/InputChannel;"); + + GET_FIELD_ID(gInputWindowClassInfo.layoutParamsFlags, gInputWindowClassInfo.clazz, + "layoutParamsFlags", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.layoutParamsType, gInputWindowClassInfo.clazz, + "layoutParamsType", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.dispatchingTimeoutNanos, gInputWindowClassInfo.clazz, + "dispatchingTimeoutNanos", "J"); + + GET_FIELD_ID(gInputWindowClassInfo.frameLeft, gInputWindowClassInfo.clazz, + "frameLeft", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.frameTop, gInputWindowClassInfo.clazz, + "frameTop", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.touchableAreaLeft, gInputWindowClassInfo.clazz, + "touchableAreaLeft", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.touchableAreaTop, gInputWindowClassInfo.clazz, + "touchableAreaTop", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.touchableAreaRight, gInputWindowClassInfo.clazz, + "touchableAreaRight", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.touchableAreaBottom, gInputWindowClassInfo.clazz, + "touchableAreaBottom", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.visible, gInputWindowClassInfo.clazz, + "visible", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.hasFocus, gInputWindowClassInfo.clazz, + "hasFocus", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.hasWallpaper, gInputWindowClassInfo.clazz, + "hasWallpaper", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.paused, gInputWindowClassInfo.clazz, + "paused", "Z"); + + GET_FIELD_ID(gInputWindowClassInfo.ownerPid, gInputWindowClassInfo.clazz, + "ownerPid", "I"); + + GET_FIELD_ID(gInputWindowClassInfo.ownerUid, gInputWindowClassInfo.clazz, + "ownerUid", "I"); + + // InputApplication + + FIND_CLASS(gInputApplicationClassInfo.clazz, "com/android/server/InputApplication"); - FIND_CLASS(gInputTargetListClassInfo.clazz, "com/android/server/InputTargetList"); + GET_FIELD_ID(gInputApplicationClassInfo.name, gInputApplicationClassInfo.clazz, + "name", "Ljava/lang/String;"); - GET_METHOD_ID(gInputTargetListClassInfo.ctor, gInputTargetListClassInfo.clazz, - "<init>", "()V"); + GET_FIELD_ID(gInputApplicationClassInfo.dispatchingTimeoutNanos, + gInputApplicationClassInfo.clazz, + "dispatchingTimeoutNanos", "J"); - GET_FIELD_ID(gInputTargetListClassInfo.mArray, gInputTargetListClassInfo.clazz, - "mArray", "[Landroid/view/InputTarget;"); + GET_FIELD_ID(gInputApplicationClassInfo.token, gInputApplicationClassInfo.clazz, + "token", "Ljava/lang/Object;"); return 0; } |