diff options
Diffstat (limited to 'services')
-rw-r--r-- | services/java/com/android/server/InputManager.java | 460 | ||||
-rw-r--r-- | services/java/com/android/server/InputTargetList.java | 105 | ||||
-rw-r--r-- | services/java/com/android/server/KeyInputQueue.java | 8 | ||||
-rw-r--r-- | services/java/com/android/server/WindowManagerService.java | 523 | ||||
-rw-r--r-- | services/jni/Android.mk | 2 | ||||
-rw-r--r-- | services/jni/com_android_server_InputManager.cpp | 746 | ||||
-rw-r--r-- | services/jni/com_android_server_KeyInputQueue.cpp | 14 | ||||
-rw-r--r-- | services/jni/onload.cpp | 2 |
8 files changed, 1799 insertions, 61 deletions
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java new file mode 100644 index 0000000..72c4166 --- /dev/null +++ b/services/java/com/android/server/InputManager.java @@ -0,0 +1,460 @@ +/* + * 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 com.android.internal.util.XmlUtils; +import com.android.server.KeyInputQueue.VirtualKey; + +import org.xmlpull.v1.XmlPullParser; + +import android.content.Context; +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; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.ArrayList; + +/* + * Wraps the C++ InputManager and provides its callbacks. + * + * XXX Tempted to promote this to a first-class service, ie. InputManagerService, to + * improve separation of concerns with respect to the window manager. + */ +public class InputManager { + static final String TAG = "InputManager"; + + private final Callbacks mCallbacks; + private final Context mContext; + private final WindowManagerService mWindowManagerService; + private final WindowManagerPolicy mWindowManagerPolicy; + private final PowerManager mPowerManager; + private final PowerManagerService mPowerManagerService; + + private int mTouchScreenConfig; + private int mKeyboardConfig; + private int mNavigationConfig; + + private static native void nativeInit(Callbacks callbacks); + private static native void nativeStart(); + private static native void nativeSetDisplaySize(int displayId, int width, int height); + private static native void nativeSetDisplayOrientation(int displayId, int rotation); + + private static native int nativeGetScanCodeState(int deviceId, int deviceClasses, + int scanCode); + private static native int nativeGetKeyCodeState(int deviceId, int deviceClasses, + int keyCode); + private static native int nativeGetSwitchState(int deviceId, int deviceClasses, + int sw); + private static native boolean nativeHasKeys(int[] keyCodes, boolean[] keyExists); + private static native void nativeRegisterInputChannel(InputChannel inputChannel); + private static native void nativeUnregisterInputChannel(InputChannel inputChannel); + + // Device class as defined by EventHub. + private static final int CLASS_KEYBOARD = 0x00000001; + private static final int CLASS_ALPHAKEY = 0x00000002; + private static final int CLASS_TOUCHSCREEN = 0x00000004; + private static final int CLASS_TRACKBALL = 0x00000008; + private static final int CLASS_TOUCHSCREEN_MT = 0x00000010; + private static final int CLASS_DPAD = 0x00000020; + + public InputManager(Context context, + WindowManagerService windowManagerService, + WindowManagerPolicy windowManagerPolicy, + PowerManager powerManager, + PowerManagerService powerManagerService) { + this.mContext = context; + this.mWindowManagerService = windowManagerService; + this.mWindowManagerPolicy = windowManagerPolicy; + this.mPowerManager = powerManager; + this.mPowerManagerService = powerManagerService; + + this.mCallbacks = new Callbacks(); + + mTouchScreenConfig = Configuration.TOUCHSCREEN_NOTOUCH; + mKeyboardConfig = Configuration.KEYBOARD_NOKEYS; + mNavigationConfig = Configuration.NAVIGATION_NONAV; + + init(); + } + + private void init() { + Slog.i(TAG, "Initializing input manager"); + nativeInit(mCallbacks); + } + + public void start() { + Slog.i(TAG, "Starting input manager"); + nativeStart(); + } + + public void setDisplaySize(int displayId, int width, int height) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Invalid display id or dimensions."); + } + + Slog.i(TAG, "Setting display #" + displayId + " size to " + width + "x" + height); + nativeSetDisplaySize(displayId, width, height); + } + + public void setDisplayOrientation(int displayId, int rotation) { + if (rotation < Surface.ROTATION_0 || rotation > Surface.ROTATION_270) { + throw new IllegalArgumentException("Invalid rotation."); + } + + Slog.i(TAG, "Setting display #" + displayId + " orientation to " + rotation); + nativeSetDisplayOrientation(displayId, rotation); + } + + public void getInputConfiguration(Configuration config) { + if (config == null) { + throw new IllegalArgumentException("config must not be null."); + } + + config.touchscreen = mTouchScreenConfig; + config.keyboard = mKeyboardConfig; + config.navigation = mNavigationConfig; + } + + public int getScancodeState(int code) { + return nativeGetScanCodeState(0, -1, code); + } + + public int getScancodeState(int deviceId, int code) { + return nativeGetScanCodeState(deviceId, -1, code); + } + + public int getTrackballScancodeState(int code) { + return nativeGetScanCodeState(-1, CLASS_TRACKBALL, code); + } + + public int getDPadScancodeState(int code) { + return nativeGetScanCodeState(-1, CLASS_DPAD, code); + } + + public int getKeycodeState(int code) { + return nativeGetKeyCodeState(0, -1, code); + } + + public int getKeycodeState(int deviceId, int code) { + return nativeGetKeyCodeState(deviceId, -1, code); + } + + public int getTrackballKeycodeState(int code) { + return nativeGetKeyCodeState(-1, CLASS_TRACKBALL, code); + } + + public int getDPadKeycodeState(int code) { + return nativeGetKeyCodeState(-1, CLASS_DPAD, code); + } + + public int getSwitchState(int sw) { + return nativeGetSwitchState(-1, -1, sw); + } + + public int getSwitchState(int deviceId, int sw) { + return nativeGetSwitchState(deviceId, -1, sw); + } + + public boolean hasKeys(int[] keyCodes, boolean[] keyExists) { + if (keyCodes == null) { + throw new IllegalArgumentException("keyCodes must not be null."); + } + if (keyExists == null) { + throw new IllegalArgumentException("keyExists must not be null."); + } + + return nativeHasKeys(keyCodes, keyExists); + } + + public void registerInputChannel(InputChannel inputChannel) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null."); + } + + nativeRegisterInputChannel(inputChannel); + } + + public void unregisterInputChannel(InputChannel inputChannel) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null."); + } + + nativeUnregisterInputChannel(inputChannel); + } + + // TBD where this really belongs, duplicate copy in WindowManagerService + static final int INJECT_FAILED = 0; + static final int INJECT_SUCCEEDED = 1; + static final int INJECT_NO_PERMISSION = -1; + + /** + * Injects a key event into the event system on behalf of an application. + * @param event The event to inject. + * @param nature The nature of the event. + * @param sync If true, waits for the event to be completed before returning. + * @param pid The pid of the injecting application. + * @param uid The uid of the injecting application. + * @return INJECT_SUCCEEDED, INJECT_FAILED or INJECT_NO_PERMISSION + */ + public int injectKeyEvent(KeyEvent event, int nature, boolean sync, int pid, int uid) { + // TODO + return INJECT_FAILED; + } + + /** + * Injects a motion event into the event system on behalf of an application. + * @param event The event to inject. + * @param nature The nature of the event. + * @param sync If true, waits for the event to be completed before returning. + * @param pid The pid of the injecting application. + * @param uid The uid of the injecting application. + * @return INJECT_SUCCEEDED, INJECT_FAILED or INJECT_NO_PERMISSION + */ + public int injectMotionEvent(MotionEvent event, int nature, boolean sync, int pid, int uid) { + // TODO + return INJECT_FAILED; + } + + public void dump(PrintWriter pw) { + // TODO + } + + private static final class VirtualKeyDefinition { + public int scanCode; + + // configured position data, specified in display coords + public int centerX; + public int centerY; + public int width; + public int height; + } + + /* + * Callbacks from native. + */ + private class Callbacks { + static final String TAG = "InputManager-Callbacks"; + + private static final boolean DEBUG_VIRTUAL_KEYS = false; + private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; + + private final InputTargetList mReusableInputTargetList = new InputTargetList(); + + @SuppressWarnings("unused") + public boolean isScreenOn() { + return mPowerManagerService.isScreenOn(); + } + + @SuppressWarnings("unused") + public boolean isScreenBright() { + return mPowerManagerService.isScreenBright(); + } + + @SuppressWarnings("unused") + public void virtualKeyFeedback(long whenNanos, int deviceId, int action, int flags, + int keyCode, int scanCode, int metaState, long downTimeNanos) { + KeyEvent keyEvent = new KeyEvent(downTimeNanos / 1000000, + whenNanos / 1000000, action, keyCode, 0, metaState, scanCode, deviceId, + flags); + + mWindowManagerService.virtualKeyFeedback(keyEvent); + } + + @SuppressWarnings("unused") + public void notifyConfigurationChanged(long whenNanos, + int touchScreenConfig, int keyboardConfig, int navigationConfig) { + mTouchScreenConfig = touchScreenConfig; + mKeyboardConfig = keyboardConfig; + mNavigationConfig = navigationConfig; + + mWindowManagerService.sendNewConfiguration(); + } + + @SuppressWarnings("unused") + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { + mWindowManagerPolicy.notifyLidSwitchChanged(whenNanos, lidOpen); + } + + @SuppressWarnings("unused") + public int hackInterceptKey(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); + } + + @SuppressWarnings("unused") + public void goToSleep(long whenNanos) { + long when = whenNanos / 1000000; + mPowerManager.goToSleep(when); + } + + @SuppressWarnings("unused") + public void pokeUserActivityForKey(long whenNanos) { + long when = whenNanos / 1000000; + mPowerManagerService.userActivity(when, false, + LocalPowerManager.BUTTON_EVENT, false); + } + + @SuppressWarnings("unused") + public void notifyAppSwitchComing() { + mWindowManagerService.mKeyWaiter.appSwitchComing(); + } + + @SuppressWarnings("unused") + public boolean filterTouchEvents() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_filterTouchEvents); + } + + @SuppressWarnings("unused") + public boolean filterJumpyTouchEvents() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_filterJumpyTouchEvents); + } + + @SuppressWarnings("unused") + public VirtualKeyDefinition[] getVirtualKeyDefinitions(String deviceName) { + ArrayList<VirtualKeyDefinition> keys = new ArrayList<VirtualKeyDefinition>(); + + try { + FileInputStream fis = new FileInputStream( + "/sys/board_properties/virtualkeys." + deviceName); + InputStreamReader isr = new InputStreamReader(fis); + BufferedReader br = new BufferedReader(isr, 2048); + String str = br.readLine(); + if (str != null) { + String[] it = str.split(":"); + if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "***** VIRTUAL KEYS: " + it); + final int N = it.length-6; + for (int i=0; i<=N; i+=6) { + if (!"0x01".equals(it[i])) { + Slog.w(TAG, "Unknown virtual key type at elem #" + i + + ": " + it[i]); + continue; + } + try { + VirtualKeyDefinition key = new VirtualKeyDefinition(); + key.scanCode = Integer.parseInt(it[i+1]); + key.centerX = Integer.parseInt(it[i+2]); + key.centerY = Integer.parseInt(it[i+3]); + key.width = Integer.parseInt(it[i+4]); + key.height = Integer.parseInt(it[i+5]); + if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Virtual key " + + key.scanCode + ": center=" + key.centerX + "," + + key.centerY + " size=" + key.width + "x" + + key.height); + keys.add(key); + } catch (NumberFormatException e) { + Slog.w(TAG, "Bad number at region " + i + " in: " + + str, e); + } + } + } + br.close(); + } catch (FileNotFoundException e) { + Slog.i(TAG, "No virtual keys found"); + } catch (IOException e) { + Slog.w(TAG, "Error reading virtual keys", e); + } + + return keys.toArray(new VirtualKeyDefinition[keys.size()]); + } + + @SuppressWarnings("unused") + public String[] getExcludedDeviceNames() { + ArrayList<String> names = new ArrayList<String>(); + + // Read partner-provided list of excluded input devices + XmlPullParser parser = null; + // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system". + File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH); + FileReader confreader = null; + try { + confreader = new FileReader(confFile); + parser = Xml.newPullParser(); + parser.setInput(confreader); + XmlUtils.beginDocument(parser, "devices"); + + while (true) { + XmlUtils.nextElement(parser); + if (!"device".equals(parser.getName())) { + break; + } + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + names.add(name); + } + } + } catch (FileNotFoundException e) { + // It's ok if the file does not exist. + } catch (Exception e) { + Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e); + } finally { + try { if (confreader != null) confreader.close(); } catch (IOException e) { } + } + + return names.toArray(new String[names.size()]); + } + + @SuppressWarnings("unused") + public InputTarget[] getKeyEventTargets(KeyEvent event, int nature, int policyFlags) { + mReusableInputTargetList.clear(); + + mWindowManagerService.getKeyEventTargets(mReusableInputTargetList, + event, nature, policyFlags); + + return mReusableInputTargetList.toNullTerminatedArray(); + } + + @SuppressWarnings("unused") + public InputTarget[] getMotionEventTargets(MotionEvent event, int nature, int policyFlags) { + mReusableInputTargetList.clear(); + + mWindowManagerService.getMotionEventTargets(mReusableInputTargetList, + event, nature, policyFlags); + + return mReusableInputTargetList.toNullTerminatedArray(); + } + } +} diff --git a/services/java/com/android/server/InputTargetList.java b/services/java/com/android/server/InputTargetList.java new file mode 100644 index 0000000..1575612 --- /dev/null +++ b/services/java/com/android/server/InputTargetList.java @@ -0,0 +1,105 @@ +/* + * 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 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] could be set to null here but we do it in toNullTerminatedArray() + } + + /** + * 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] could be set to null here but we do it in toNullTerminatedArray() + } + + /** + * Gets the input targets as a null-terminated array. + * @return The input target array. + */ + public InputTarget[] toNullTerminatedArray() { + mArray[mCount] = null; + return mArray; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java index f30346b..f62c7ee 100644 --- a/services/java/com/android/server/KeyInputQueue.java +++ b/services/java/com/android/server/KeyInputQueue.java @@ -298,7 +298,9 @@ public abstract class KeyInputQueue { mHapticFeedbackCallback = hapticFeedbackCallback; - readExcludedDevices(); + if (! WindowManagerService.ENABLE_NATIVE_INPUT_DISPATCH) { + readExcludedDevices(); + } PowerManager pm = (PowerManager)context.getSystemService( Context.POWER_SERVICE); @@ -311,7 +313,9 @@ public abstract class KeyInputQueue { mFirst.next = mLast; mLast.prev = mFirst; - mThread.start(); + if (! WindowManagerService.ENABLE_NATIVE_INPUT_DISPATCH) { + mThread.start(); + } } public void setDisplay(Display display) { diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index ac5e3f1..9bc3931 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -101,6 +101,9 @@ import android.view.IRotationWatcher; import android.view.IWindow; 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; @@ -157,6 +160,8 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean SHOW_TRANSACTIONS = false; static final boolean HIDE_STACK_CRAWLS = true; static final boolean MEASURE_LATENCY = false; + static final boolean ENABLE_NATIVE_INPUT_DISPATCH = + WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH; static private LatencyTimer lt; static final boolean PROFILE_ORIENTATION = false; @@ -497,10 +502,12 @@ public class WindowManagerService extends IWindowManager.Stub final KeyWaiter mKeyWaiter = new KeyWaiter(); final KeyQ mQueue; + final InputManager mInputManager; final InputDispatcherThread mInputThread; // Who is holding the screen on. Session mHoldingScreenOn; + PowerManager.WakeLock mHoldingScreenWakeLock; boolean mTurnOnScreen; @@ -650,8 +657,16 @@ public class WindowManagerService extends IWindowManager.Stub } mMinWaitTimeBetweenTouchEvents = 1000 / max_events_per_sec; - mQueue = new KeyQ(); + mHoldingScreenWakeLock = pmc.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, + "KEEP_SCREEN_ON_FLAG"); + mHoldingScreenWakeLock.setReferenceCounted(false); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputManager = new InputManager(context, this, mPolicy, pmc, mPowerManager); + } else { + mInputManager = null; + } + mQueue = new KeyQ(); mInputThread = new InputDispatcherThread(); PolicyThread thr = new PolicyThread(mPolicy, this, context, pm); @@ -666,7 +681,11 @@ public class WindowManagerService extends IWindowManager.Stub } } - mInputThread.start(); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputManager.start(); + } else { + mInputThread.start(); + } // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); @@ -1859,7 +1878,7 @@ public class WindowManagerService extends IWindowManager.Stub public int addWindow(Session session, IWindow client, WindowManager.LayoutParams attrs, int viewVisibility, - Rect outContentInsets) { + Rect outContentInsets, InputChannel outInputChannel) { int res = mPolicy.checkAddPermission(attrs); if (res != WindowManagerImpl.ADD_OKAY) { return res; @@ -1878,7 +1897,12 @@ public class WindowManagerService extends IWindowManager.Stub mDisplay = wm.getDefaultDisplay(); mInitialDisplayWidth = mDisplay.getWidth(); mInitialDisplayHeight = mDisplay.getHeight(); - mQueue.setDisplay(mDisplay); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputManager.setDisplaySize(0, + mInitialDisplayWidth, mInitialDisplayHeight); + } else { + mQueue.setDisplay(mDisplay); + } reportNewConfig = true; } @@ -1971,6 +1995,17 @@ public class WindowManagerService extends IWindowManager.Stub if (res != WindowManagerImpl.ADD_OKAY) { return res; } + + if (ENABLE_NATIVE_INPUT_DISPATCH) { + if (outInputChannel != null) { + String name = win.makeInputChannelName(); + InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); + win.mInputChannel = inputChannels[0]; + inputChannels[1].transferToBinderOutParameter(outInputChannel); + + mInputManager.registerInputChannel(win.mInputChannel); + } + } // From now on, no exceptions or errors allowed! @@ -4354,7 +4389,11 @@ public class WindowManagerService extends IWindowManager.Stub "getSwitchState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return KeyInputQueue.getSwitchState(sw); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.getSwitchState(sw); + } else { + return KeyInputQueue.getSwitchState(sw); + } } public int getSwitchStateForDevice(int devid, int sw) { @@ -4362,7 +4401,11 @@ public class WindowManagerService extends IWindowManager.Stub "getSwitchStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return KeyInputQueue.getSwitchState(devid, sw); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.getSwitchState(devid, sw); + } else { + return KeyInputQueue.getSwitchState(devid, sw); + } } public int getScancodeState(int sw) { @@ -4370,7 +4413,11 @@ public class WindowManagerService extends IWindowManager.Stub "getScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getScancodeState(sw); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.getScancodeState(sw); + } else { + return mQueue.getScancodeState(sw); + } } public int getScancodeStateForDevice(int devid, int sw) { @@ -4378,7 +4425,11 @@ public class WindowManagerService extends IWindowManager.Stub "getScancodeStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getScancodeState(devid, sw); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.getScancodeState(devid, sw); + } else { + return mQueue.getScancodeState(devid, sw); + } } public int getTrackballScancodeState(int sw) { @@ -4386,7 +4437,11 @@ public class WindowManagerService extends IWindowManager.Stub "getTrackballScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getTrackballScancodeState(sw); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.getTrackballScancodeState(sw); + } else { + return mQueue.getTrackballScancodeState(sw); + } } public int getDPadScancodeState(int sw) { @@ -4394,7 +4449,11 @@ public class WindowManagerService extends IWindowManager.Stub "getDPadScancodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getDPadScancodeState(sw); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.getDPadScancodeState(sw); + } else { + return mQueue.getDPadScancodeState(sw); + } } public int getKeycodeState(int sw) { @@ -4402,7 +4461,11 @@ public class WindowManagerService extends IWindowManager.Stub "getKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getKeycodeState(sw); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.getKeycodeState(sw); + } else { + return mQueue.getKeycodeState(sw); + } } public int getKeycodeStateForDevice(int devid, int sw) { @@ -4410,7 +4473,11 @@ public class WindowManagerService extends IWindowManager.Stub "getKeycodeStateForDevice()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getKeycodeState(devid, sw); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.getKeycodeState(devid, sw); + } else { + return mQueue.getKeycodeState(devid, sw); + } } public int getTrackballKeycodeState(int sw) { @@ -4418,7 +4485,11 @@ public class WindowManagerService extends IWindowManager.Stub "getTrackballKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getTrackballKeycodeState(sw); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.getTrackballKeycodeState(sw); + } else { + return mQueue.getTrackballKeycodeState(sw); + } } public int getDPadKeycodeState(int sw) { @@ -4426,11 +4497,19 @@ public class WindowManagerService extends IWindowManager.Stub "getDPadKeycodeState()")) { throw new SecurityException("Requires READ_INPUT_STATE permission"); } - return mQueue.getDPadKeycodeState(sw); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.getDPadKeycodeState(sw); + } else { + return mQueue.getDPadKeycodeState(sw); + } } public boolean hasKeys(int[] keycodes, boolean[] keyExists) { - return KeyInputQueue.hasKeys(keycodes, keyExists); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + return mInputManager.hasKeys(keycodes, keyExists); + } else { + return KeyInputQueue.hasKeys(keycodes, keyExists); + } } public void enableScreenAfterBoot() { @@ -4575,7 +4654,11 @@ public class WindowManagerService extends IWindowManager.Stub mLayoutNeeded = true; startFreezingDisplayLocked(); Slog.i(TAG, "Setting rotation to " + rotation + ", animFlags=" + animFlags); - mQueue.setOrientation(rotation); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputManager.setDisplayOrientation(0, rotation); + } else { + mQueue.setOrientation(rotation); + } if (mDisplayEnabled) { Surface.setOrientation(0, rotation, animFlags); } @@ -4906,7 +4989,11 @@ public class WindowManagerService extends IWindowManager.Stub if (mDisplay == null) { return false; } - mQueue.getInputConfiguration(config); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + mInputManager.getInputConfiguration(config); + } else { + mQueue.getInputConfiguration(config); + } // Use the effective "visual" dimensions based on current rotation final boolean rotated = (mRotation == Surface.ROTATION_90 @@ -4989,6 +5076,291 @@ public class WindowManagerService extends IWindowManager.Stub // ------------------------------------------------------------- // Input Events and Focus Management // ------------------------------------------------------------- + + public void getKeyEventTargets(InputTargetList inputTargets, + KeyEvent event, int nature, int policyFlags) { + 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? + + WindowState focus = getFocusedWindow(); + wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT); + + addInputTarget(inputTargets, focus, InputTarget.FLAG_SYNC); + } + + // Target of Motion events + WindowState mTouchFocus; + + // Windows above the target who would like to receive an "outside" + // touch event for any down events outside of them. + // (This is a linked list by way of WindowState.mNextOutsideTouch.) + WindowState mOutsideTouchTargets; + + private void clearTouchFocus() { + mTouchFocus = null; + mOutsideTouchTargets = null; + } + + public void getMotionEventTargets(InputTargetList inputTargets, + MotionEvent event, int nature, int policyFlags) { + if (nature == InputQueue.INPUT_EVENT_NATURE_TRACKBALL) { + // More or less the same as for keys... + WindowState focus = getFocusedWindow(); + wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT); + + addInputTarget(inputTargets, focus, InputTarget.FLAG_SYNC); + return; + } + + int action = event.getAction(); + + // TODO detect cheek presses somewhere... either here or in native code + + final boolean screenWasOff = (policyFlags & WindowManagerPolicy.FLAG_BRIGHT_HERE) != 0; + + WindowState target = mTouchFocus; + + if (action == MotionEvent.ACTION_UP) { + // let go of our target + mPowerManager.logPointerUpEvent(); + clearTouchFocus(); + } else if (action == MotionEvent.ACTION_DOWN) { + // acquire a new target + mPowerManager.logPointerDownEvent(); + + synchronized (mWindowMap) { + if (mTouchFocus != null) { + // this is weird, we got a pen 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); + clearTouchFocus(); + } + + // ACTION_DOWN is special, because we need to lock next events to + // the window we'll land onto. + final int x = (int) event.getX(); + final int y = (int) event.getY(); + + 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); + final int flags = child.mAttrs.flags; + if ((flags & WindowManager.LayoutParams.FLAG_SYSTEM_ERROR) != 0) { + if (topErrWindow == null) { + topErrWindow = child; + } + } + if (!child.isVisibleLw()) { + //Slog.i(TAG, "Not visible!"); + continue; + } + if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { + //Slog.i(TAG, "Not touchable!"); + if ((flags & WindowManager.LayoutParams + .FLAG_WATCH_OUTSIDE_TOUCH) != 0) { + child.mNextOutsideTouch = mOutsideTouchTargets; + mOutsideTouchTargets = child; + } + continue; + } + 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; + } + break; + } + + if ((flags & WindowManager.LayoutParams + .FLAG_WATCH_OUTSIDE_TOUCH) != 0) { + child.mNextOutsideTouch = mOutsideTouchTargets; + mOutsideTouchTargets = child; + //Slog.i(TAG, "Adding to outside target list: " + 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; + } + } + + target = mTouchFocus; + } + + if (target != null) { + wakeupIfNeeded(target, eventType(event)); + } + + int targetFlags = 0; + if (target == 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); + } + } else { + if ((target.mAttrs.flags & + WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) != 0) { + //target wants to ignore fat touch events + boolean cheekPress = mPolicy.isCheekPressedAgainstScreen(event); + //explicit flag to return without processing event further + boolean returnFlag = false; + if((action == MotionEvent.ACTION_DOWN)) { + mFatTouch = false; + if(cheekPress) { + mFatTouch = true; + returnFlag = true; + } + } else { + if(action == MotionEvent.ACTION_UP) { + if(mFatTouch) { + //earlier even was invalid doesnt matter if current up is cheekpress or not + mFatTouch = false; + returnFlag = true; + } else if(cheekPress) { + //cancel the earlier event + targetFlags |= InputTarget.FLAG_CANCEL; + action = MotionEvent.ACTION_CANCEL; + } + } else if(action == MotionEvent.ACTION_MOVE) { + if(mFatTouch) { + //two cases here + //an invalid down followed by 0 or moves(valid or invalid) + //a valid down, invalid move, more moves. want to ignore till up + returnFlag = true; + } else if(cheekPress) { + //valid down followed by invalid moves + //an invalid move have to cancel earlier action + targetFlags |= InputTarget.FLAG_CANCEL; + action = MotionEvent.ACTION_CANCEL; + if (DEBUG_INPUT) Slog.v(TAG, "Sending cancel for invalid ACTION_MOVE"); + //note that the subsequent invalid moves will not get here + mFatTouch = true; + } + } + } //else if action + if(returnFlag) { + return; + } + } //end if target + } + + synchronized (mWindowMap) { + if (target != null && ! target.isVisibleLw()) { + target = null; + } + + if (action == MotionEvent.ACTION_DOWN) { + while (mOutsideTouchTargets != null) { + addInputTarget(inputTargets, mOutsideTouchTargets, + InputTarget.FLAG_OUTSIDE | targetFlags); + mOutsideTouchTargets = mOutsideTouchTargets.mNextOutsideTouch; + } + } + + // If we sent an initial down to the wallpaper, then continue + // sending events until the final up. + // Alternately if we are on top of the wallpaper, then the wallpaper also + // gets to see this movement. + if (mSendingPointersToWallpaper || + (target != null && action == MotionEvent.ACTION_DOWN + && mWallpaperTarget == target + && target.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) { + continue; + } + + switch (action) { + case MotionEvent.ACTION_DOWN: + mSendingPointersToWallpaper = true; + break; + case MotionEvent.ACTION_UP: + mSendingPointersToWallpaper = false; + break; + } + + addInputTarget(inputTargets, wallpaper, targetFlags); + } + } + } + + if (target != null) { + addInputTarget(inputTargets, target, InputTarget.FLAG_SYNC | targetFlags); + } + } + } + + private void addInputTarget(InputTargetList inputTargets, WindowState window, int flags) { + if (window.mInputChannel == null) { + return; + } + + 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); + } + } + + inputTargets.add(window.mInputChannel, flags, timeoutNanos, + - window.mFrame.left, - window.mFrame.top); + } private final void wakeupIfNeeded(WindowState targetWin, int eventType) { long curTime = SystemClock.uptimeMillis(); @@ -5499,10 +5871,18 @@ public class WindowManagerService extends IWindowManager.Stub final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); - final int result = dispatchKey(newEvent, pid, uid); - if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); + + final int result; + if (ENABLE_NATIVE_INPUT_DISPATCH) { + result = mInputManager.injectKeyEvent(newEvent, + InputQueue.INPUT_EVENT_NATURE_KEY, sync, pid, uid); + } else { + result = dispatchKey(newEvent, pid, uid); + if (sync) { + mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); + } } + Binder.restoreCallingIdentity(ident); switch (result) { case INJECT_NO_PERMISSION: @@ -5527,10 +5907,18 @@ public class WindowManagerService extends IWindowManager.Stub final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); - final int result = dispatchPointer(null, ev, pid, uid); - if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); + + final int result; + if (ENABLE_NATIVE_INPUT_DISPATCH) { + result = mInputManager.injectMotionEvent(ev, + InputQueue.INPUT_EVENT_NATURE_TOUCH, sync, pid, uid); + } else { + result = dispatchPointer(null, ev, pid, uid); + if (sync) { + mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); + } } + Binder.restoreCallingIdentity(ident); switch (result) { case INJECT_NO_PERMISSION: @@ -5555,10 +5943,18 @@ public class WindowManagerService extends IWindowManager.Stub final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); - final int result = dispatchTrackball(null, ev, pid, uid); - if (sync) { - mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); + + final int result; + if (ENABLE_NATIVE_INPUT_DISPATCH) { + result = mInputManager.injectMotionEvent(ev, + InputQueue.INPUT_EVENT_NATURE_TRACKBALL, sync, pid, uid); + } else { + result = dispatchTrackball(null, ev, pid, uid); + if (sync) { + mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid); + } } + Binder.restoreCallingIdentity(ident); switch (result) { case INJECT_NO_PERMISSION: @@ -6326,14 +6722,8 @@ public class WindowManagerService extends IWindowManager.Stub private class KeyQ extends KeyInputQueue implements KeyInputQueue.FilterCallback { - PowerManager.WakeLock mHoldingScreen; - KeyQ() { super(mContext, WindowManagerService.this); - PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - mHoldingScreen = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, - "KEEP_SCREEN_ON_FLAG"); - mHoldingScreen.setReferenceCounted(false); } @Override @@ -6445,21 +6835,6 @@ public class WindowManagerService extends IWindowManager.Stub return FILTER_KEEP; } } - - /** - * Must be called with the main window manager lock held. - */ - void setHoldScreenLocked(boolean holding) { - boolean state = mHoldingScreen.isHeld(); - if (holding != state) { - if (holding) { - mHoldingScreen.acquire(); - } else { - mPolicy.screenOnStoppedLw(); - mHoldingScreen.release(); - } - } - } } public boolean detectSafeMode() { @@ -6788,8 +7163,14 @@ public class WindowManagerService extends IWindowManager.Stub } public int add(IWindow window, WindowManager.LayoutParams attrs, + int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) { + return addWindow(this, window, attrs, viewVisibility, outContentInsets, + outInputChannel); + } + + public int addWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, Rect outContentInsets) { - return addWindow(this, window, attrs, viewVisibility, outContentInsets); + return addWindow(this, window, attrs, viewVisibility, outContentInsets, null); } public void remove(IWindow window) { @@ -7158,6 +7539,9 @@ public class WindowManagerService extends IWindowManager.Stub int mSurfaceLayer; float mSurfaceAlpha; + // Input channel + InputChannel mInputChannel; + WindowState(Session s, IWindow c, WindowToken token, WindowState attachedWindow, WindowManager.LayoutParams a, int viewVisibility) { @@ -8182,6 +8566,15 @@ public class WindowManagerService extends IWindowManager.Stub // Ignore if it has already been removed (usually because // we are doing this as part of processing a death note.) } + + if (ENABLE_NATIVE_INPUT_DISPATCH) { + if (mInputChannel != null) { + mInputManager.unregisterInputChannel(mInputChannel); + + mInputChannel.dispose(); + mInputChannel = null; + } + } } private class DeathRecipient implements IBinder.DeathRecipient { @@ -8424,6 +8817,11 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep); } } + + String makeInputChannelName() { + return Integer.toHexString(System.identityHashCode(this)) + + " " + mAttrs.getTitle(); + } @Override public String toString() { @@ -9275,7 +9673,8 @@ public class WindowManagerService extends IWindowManager.Stub IInputContext inputContext) { if (client == null) throw new IllegalArgumentException("null client"); if (inputContext == null) throw new IllegalArgumentException("null inputContext"); - return new Session(client, inputContext); + Session session = new Session(client, inputContext); + return session; } public boolean inputMethodClientHasFocus(IInputMethodClient client) { @@ -10773,7 +11172,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen + " holdScreen=" + holdScreen); if (!mDisplayFrozen) { - mQueue.setHoldScreenLocked(holdScreen != null); + setHoldScreenLocked(holdScreen != null); if (screenBrightness < 0 || screenBrightness > 1.0f) { mPowerManager.setScreenBrightnessOverride(-1); } else { @@ -10804,6 +11203,21 @@ public class WindowManagerService extends IWindowManager.Stub // be enabled, because the window obscured flags have changed. enableScreenIfNeededLocked(); } + + /** + * Must be called with the main window manager lock held. + */ + void setHoldScreenLocked(boolean holding) { + boolean state = mHoldingScreenWakeLock.isHeld(); + if (holding != state) { + if (holding) { + mHoldingScreenWakeLock.acquire(); + } else { + mPolicy.screenOnStoppedLw(); + mHoldingScreenWakeLock.release(); + } + } + } void requestAnimationLocked(long delay) { if (!mAnimationPending) { @@ -11138,8 +11552,13 @@ public class WindowManagerService extends IWindowManager.Stub return; } - pw.println("Input State:"); - mQueue.dump(pw, " "); + if (ENABLE_NATIVE_INPUT_DISPATCH) { + pw.println("Input Dispatcher State:"); + mInputManager.dump(pw); + } else { + pw.println("Input State:"); + mQueue.dump(pw, " "); + } pw.println(" "); synchronized(mWindowMap) { diff --git a/services/jni/Android.mk b/services/jni/Android.mk index b90e327..499ca86 100644 --- a/services/jni/Android.mk +++ b/services/jni/Android.mk @@ -5,6 +5,7 @@ LOCAL_SRC_FILES:= \ com_android_server_AlarmManagerService.cpp \ com_android_server_BatteryService.cpp \ com_android_server_KeyInputQueue.cpp \ + com_android_server_InputManager.cpp \ com_android_server_LightsService.cpp \ com_android_server_SensorService.cpp \ com_android_server_SystemServer.cpp \ @@ -16,6 +17,7 @@ LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) LOCAL_SHARED_LIBRARIES := \ + libandroid_runtime \ libcutils \ libhardware \ libhardware_legacy \ diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp new file mode 100644 index 0000000..53262ae --- /dev/null +++ b/services/jni/com_android_server_InputManager.cpp @@ -0,0 +1,746 @@ +/* + * 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 "InputManager-JNI" + +#include "JNIHelp.h" +#include "jni.h" +#include <android_runtime/AndroidRuntime.h> +#include <ui/InputManager.h> +#include <ui/InputTransport.h> +#include <utils/Log.h> +#include <utils/threads.h> +#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 { + +class InputDispatchPolicy : public InputDispatchPolicyInterface { +public: + InputDispatchPolicy(JNIEnv* env, jobject callbacks); + virtual ~InputDispatchPolicy(); + + void setDisplaySize(int32_t displayId, int32_t width, int32_t height); + void setDisplayOrientation(int32_t displayId, int32_t orientation); + + virtual bool getDisplayInfo(int32_t displayId, + int32_t* width, int32_t* height, int32_t* orientation); + + virtual void notifyConfigurationChanged(nsecs_t when, + int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig); + + virtual void notifyLidSwitchChanged(nsecs_t when, bool lidOpen); + + virtual void virtualKeyFeedback(nsecs_t when, int32_t deviceId, + int32_t action, int32_t flags, int32_t keyCode, + int32_t scanCode, int32_t metaState, nsecs_t downTime); + + virtual int32_t interceptKey(nsecs_t when, int32_t deviceId, + bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags); + virtual int32_t interceptTrackball(nsecs_t when, bool buttonChanged, bool buttonDown, + bool rolled); + virtual int32_t interceptTouch(nsecs_t when); + + virtual bool filterTouchEvents(); + virtual bool filterJumpyTouchEvents(); + virtual void getVirtualKeyDefinitions(const String8& deviceName, + Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions); + virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames); + + virtual bool allowKeyRepeat(); + virtual nsecs_t getKeyRepeatTimeout(); + + virtual void getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, + Vector<InputTarget>& outTargets); + virtual void getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, + Vector<InputTarget>& outTargets); + +private: + bool isScreenOn(); + bool isScreenBright(); + +private: + jobject mCallbacks; + + int32_t mFilterTouchEvents; + int32_t mFilterJumpyTouchEvents; + + Mutex mDisplayLock; + int32_t mDisplayWidth, mDisplayHeight; + int32_t mDisplayOrientation; + + inline JNIEnv* threadEnv() const { + return AndroidRuntime::getJNIEnv(); + } +}; + + +// globals + +static sp<EventHub> gEventHub; +static sp<InputDispatchPolicy> gInputDispatchPolicy; +static sp<InputManager> gInputManager; + +// JNI + +static struct { + jclass clazz; + + jmethodID isScreenOn; + jmethodID isScreenBright; + jmethodID notifyConfigurationChanged; + jmethodID notifyLidSwitchChanged; + jmethodID virtualKeyFeedback; + jmethodID hackInterceptKey; + jmethodID goToSleep; + jmethodID pokeUserActivityForKey; + jmethodID notifyAppSwitchComing; + jmethodID filterTouchEvents; + jmethodID filterJumpyTouchEvents; + jmethodID getVirtualKeyDefinitions; + jmethodID getExcludedDeviceNames; + jmethodID getKeyEventTargets; + jmethodID getMotionEventTargets; +} gCallbacksClassInfo; + +static struct { + jclass clazz; + + jfieldID scanCode; + jfieldID centerX; + jfieldID centerY; + jfieldID width; + jfieldID height; +} gVirtualKeyDefinitionClassInfo; + +static bool checkInputManagerUnitialized(JNIEnv* env) { + if (gInputManager == NULL) { + LOGE("Input manager not initialized."); + jniThrowRuntimeException(env, "Input manager not initialized."); + return true; + } + return false; +} + +static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz, + jobject callbacks) { + if (gEventHub == NULL) { + gEventHub = new EventHub(); + } + + if (gInputDispatchPolicy == NULL) { + gInputDispatchPolicy = new InputDispatchPolicy(env, callbacks); + } + + if (gInputManager == NULL) { + gInputManager = new InputManager(gEventHub, gInputDispatchPolicy); + } +} + +static void android_server_InputManager_nativeStart(JNIEnv* env, jclass clazz) { + if (checkInputManagerUnitialized(env)) { + return; + } + + status_t result = gInputManager->start(); + if (result) { + jniThrowRuntimeException(env, "Input manager could not be started."); + } +} + +static void android_server_InputManager_nativeSetDisplaySize(JNIEnv* env, jclass clazz, + jint displayId, jint width, jint height) { + if (checkInputManagerUnitialized(env)) { + return; + } + + // XXX we could get this from the SurfaceFlinger directly instead of requiring it + // to be passed in like this, not sure which is better but leaving it like this + // keeps the window manager in direct control of when display transitions propagate down + // to the input dispatcher + gInputDispatchPolicy->setDisplaySize(displayId, width, height); +} + +static void android_server_InputManager_nativeSetDisplayOrientation(JNIEnv* env, jclass clazz, + jint displayId, jint orientation) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gInputDispatchPolicy->setDisplayOrientation(displayId, orientation); +} + +static jint android_server_InputManager_nativeGetScanCodeState(JNIEnv* env, jclass clazz, + jint deviceId, jint deviceClasses, jint scanCode) { + if (checkInputManagerUnitialized(env)) { + return KEY_STATE_UNKNOWN; + } + + return gInputManager->getScanCodeState(deviceId, deviceClasses, scanCode); +} + +static jint android_server_InputManager_nativeGetKeyCodeState(JNIEnv* env, jclass clazz, + jint deviceId, jint deviceClasses, jint keyCode) { + if (checkInputManagerUnitialized(env)) { + return KEY_STATE_UNKNOWN; + } + + return gInputManager->getKeyCodeState(deviceId, deviceClasses, keyCode); +} + +static jint android_server_InputManager_nativeGetSwitchState(JNIEnv* env, jclass clazz, + jint deviceId, jint deviceClasses, jint sw) { + if (checkInputManagerUnitialized(env)) { + return KEY_STATE_UNKNOWN; + } + + return gInputManager->getSwitchState(deviceId, deviceClasses, sw); +} + +static jboolean android_server_InputManager_nativeHasKeys(JNIEnv* env, jclass clazz, + jintArray keyCodes, jbooleanArray outFlags) { + if (checkInputManagerUnitialized(env)) { + return JNI_FALSE; + } + + int32_t* codes = env->GetIntArrayElements(keyCodes, NULL); + uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL); + jsize numCodes = env->GetArrayLength(keyCodes); + jboolean result; + if (numCodes == env->GetArrayLength(outFlags)) { + result = gInputManager->hasKeys(numCodes, codes, flags); + } else { + result = JNI_FALSE; + } + + env->ReleaseBooleanArrayElements(outFlags, flags, 0); + env->ReleaseIntArrayElements(keyCodes, codes, 0); + return result; +} + +static void throwInputChannelNotInitialized(JNIEnv* env) { + jniThrowException(env, "java/lang/IllegalStateException", + "inputChannel is not initialized"); +} + +static void android_server_InputManager_handleInputChannelDisposed(JNIEnv* env, + jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) { + LOGW("Input channel object '%s' was disposed without first being unregistered with " + "the input manager!", inputChannel->getName().string()); + + gInputManager->unregisterInputChannel(inputChannel); +} + +static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, + jobject inputChannelObj) { + if (checkInputManagerUnitialized(env)) { + return; + } + + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == NULL) { + throwInputChannelNotInitialized(env); + return; + } + + status_t status = gInputManager->registerInputChannel(inputChannel); + if (status) { + jniThrowRuntimeException(env, "Failed to register input channel. " + "Check logs for details."); + return; + } + + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, + android_server_InputManager_handleInputChannelDisposed, NULL); +} + +static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, + jobject inputChannelObj) { + if (checkInputManagerUnitialized(env)) { + return; + } + + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == NULL) { + throwInputChannelNotInitialized(env); + return; + } + + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL); + + status_t status = gInputManager->unregisterInputChannel(inputChannel); + if (status) { + jniThrowRuntimeException(env, "Failed to unregister input channel. " + "Check logs for details."); + } +} + +static JNINativeMethod gInputManagerMethods[] = { + /* name, signature, funcPtr */ + { "nativeInit", "(Lcom/android/server/InputManager$Callbacks;)V", + (void*) android_server_InputManager_nativeInit }, + { "nativeStart", "()V", + (void*) android_server_InputManager_nativeStart }, + { "nativeSetDisplaySize", "(III)V", + (void*) android_server_InputManager_nativeSetDisplaySize }, + { "nativeSetDisplayOrientation", "(II)V", + (void*) android_server_InputManager_nativeSetDisplayOrientation }, + { "nativeGetScanCodeState", "(III)I", + (void*) android_server_InputManager_nativeGetScanCodeState }, + { "nativeGetKeyCodeState", "(III)I", + (void*) android_server_InputManager_nativeGetKeyCodeState }, + { "nativeGetSwitchState", "(III)I", + (void*) android_server_InputManager_nativeGetSwitchState }, + { "nativeHasKeys", "([I[Z)Z", + (void*) android_server_InputManager_nativeHasKeys }, + { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;)V", + (void*) android_server_InputManager_nativeRegisterInputChannel }, + { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V", + (void*) android_server_InputManager_nativeUnregisterInputChannel } +}; + +#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_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + var = env->GetMethodID(clazz, methodName, methodDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method " methodName); + +#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_server_InputManager(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/InputManager", + gInputManagerMethods, NELEM(gInputManagerMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + // Policy + + FIND_CLASS(gCallbacksClassInfo.clazz, "com/android/server/InputManager$Callbacks"); + + GET_METHOD_ID(gCallbacksClassInfo.isScreenOn, gCallbacksClassInfo.clazz, + "isScreenOn", "()Z"); + + GET_METHOD_ID(gCallbacksClassInfo.isScreenBright, gCallbacksClassInfo.clazz, + "isScreenBright", "()Z"); + + GET_METHOD_ID(gCallbacksClassInfo.notifyConfigurationChanged, gCallbacksClassInfo.clazz, + "notifyConfigurationChanged", "(JIII)V"); + + GET_METHOD_ID(gCallbacksClassInfo.notifyLidSwitchChanged, gCallbacksClassInfo.clazz, + "notifyLidSwitchChanged", "(JZ)V"); + + GET_METHOD_ID(gCallbacksClassInfo.virtualKeyFeedback, gCallbacksClassInfo.clazz, + "virtualKeyFeedback", "(JIIIIIIJ)V"); + + GET_METHOD_ID(gCallbacksClassInfo.hackInterceptKey, gCallbacksClassInfo.clazz, + "hackInterceptKey", "(IIIIIIJZ)I"); + + GET_METHOD_ID(gCallbacksClassInfo.goToSleep, gCallbacksClassInfo.clazz, + "goToSleep", "(J)V"); + + GET_METHOD_ID(gCallbacksClassInfo.pokeUserActivityForKey, gCallbacksClassInfo.clazz, + "pokeUserActivityForKey", "(J)V"); + + GET_METHOD_ID(gCallbacksClassInfo.notifyAppSwitchComing, gCallbacksClassInfo.clazz, + "notifyAppSwitchComing", "()V"); + + GET_METHOD_ID(gCallbacksClassInfo.filterTouchEvents, gCallbacksClassInfo.clazz, + "filterTouchEvents", "()Z"); + + GET_METHOD_ID(gCallbacksClassInfo.filterJumpyTouchEvents, gCallbacksClassInfo.clazz, + "filterJumpyTouchEvents", "()Z"); + + GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyDefinitions, gCallbacksClassInfo.clazz, + "getVirtualKeyDefinitions", + "(Ljava/lang/String;)[Lcom/android/server/InputManager$VirtualKeyDefinition;"); + + GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz, + "getExcludedDeviceNames", "()[Ljava/lang/String;"); + + GET_METHOD_ID(gCallbacksClassInfo.getKeyEventTargets, gCallbacksClassInfo.clazz, + "getKeyEventTargets", "(Landroid/view/KeyEvent;II)[Landroid/view/InputTarget;"); + + GET_METHOD_ID(gCallbacksClassInfo.getMotionEventTargets, gCallbacksClassInfo.clazz, + "getMotionEventTargets", "(Landroid/view/MotionEvent;II)[Landroid/view/InputTarget;"); + + // VirtualKeyDefinition + + FIND_CLASS(gVirtualKeyDefinitionClassInfo.clazz, + "com/android/server/InputManager$VirtualKeyDefinition"); + + GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.scanCode, gVirtualKeyDefinitionClassInfo.clazz, + "scanCode", "I"); + + GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerX, gVirtualKeyDefinitionClassInfo.clazz, + "centerX", "I"); + + GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerY, gVirtualKeyDefinitionClassInfo.clazz, + "centerY", "I"); + + GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.width, gVirtualKeyDefinitionClassInfo.clazz, + "width", "I"); + + GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.height, gVirtualKeyDefinitionClassInfo.clazz, + "height", "I"); + + return 0; +} + +// static functions + +static bool isAppSwitchKey(int32_t keyCode) { + return keyCode == KEYCODE_HOME || keyCode == KEYCODE_ENDCALL; +} + +static bool checkException(JNIEnv* env, const char* methodName) { + if (env->ExceptionCheck()) { + LOGE("An exception was thrown by an InputDispatchPolicy callback '%s'.", methodName); + LOGE_EX(env); + env->ExceptionClear(); + return true; + } + return false; +} + + +// InputDispatchPolicy implementation + +InputDispatchPolicy::InputDispatchPolicy(JNIEnv* env, jobject callbacks) : + mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), + mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(-1) { + mCallbacks = env->NewGlobalRef(callbacks); +} + +InputDispatchPolicy::~InputDispatchPolicy() { + JNIEnv* env = threadEnv(); + + env->DeleteGlobalRef(mCallbacks); +} + +void InputDispatchPolicy::setDisplaySize(int32_t displayId, int32_t width, int32_t height) { + if (displayId == 0) { + AutoMutex _l(mDisplayLock); + + mDisplayWidth = width; + mDisplayHeight = height; + } +} + +void InputDispatchPolicy::setDisplayOrientation(int32_t displayId, int32_t orientation) { + if (displayId == 0) { + AutoMutex _l(mDisplayLock); + + mDisplayOrientation = orientation; + } +} + +bool InputDispatchPolicy::getDisplayInfo(int32_t displayId, + int32_t* width, int32_t* height, int32_t* orientation) { + bool result = false; + if (displayId == 0) { + AutoMutex _l(mDisplayLock); + + if (mDisplayWidth > 0) { + *width = mDisplayWidth; + *height = mDisplayHeight; + *orientation = mDisplayOrientation; + result = true; + } + } + return result; +} + +bool InputDispatchPolicy::isScreenOn() { + JNIEnv* env = threadEnv(); + + jboolean result = env->CallBooleanMethod(mCallbacks, gCallbacksClassInfo.isScreenOn); + if (checkException(env, "isScreenOn")) { + return true; + } + return result; +} + +bool InputDispatchPolicy::isScreenBright() { + JNIEnv* env = threadEnv(); + + jboolean result = env->CallBooleanMethod(mCallbacks, gCallbacksClassInfo.isScreenBright); + if (checkException(env, "isScreenBright")) { + return true; + } + return result; +} + +void InputDispatchPolicy::notifyConfigurationChanged(nsecs_t when, + int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig) { + JNIEnv* env = threadEnv(); + + env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.notifyConfigurationChanged, + when, touchScreenConfig, keyboardConfig, navigationConfig); + checkException(env, "notifyConfigurationChanged"); +} + +void InputDispatchPolicy::notifyLidSwitchChanged(nsecs_t when, bool lidOpen) { + JNIEnv* env = threadEnv(); + env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.notifyLidSwitchChanged, + when, lidOpen); + checkException(env, "notifyLidSwitchChanged"); +} + +void InputDispatchPolicy::virtualKeyFeedback(nsecs_t when, int32_t deviceId, + int32_t action, int32_t flags, int32_t keyCode, + int32_t scanCode, int32_t metaState, nsecs_t downTime) { + JNIEnv* env = threadEnv(); + + env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.virtualKeyFeedback, + when, deviceId, action, flags, keyCode, scanCode, metaState, downTime); + checkException(env, "virtualKeyFeedback"); +} + +int32_t InputDispatchPolicy::interceptKey(nsecs_t when, + int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) { + const int32_t WM_ACTION_PASS_TO_USER = 1; + const int32_t WM_ACTION_POKE_USER_ACTIVITY = 2; + const int32_t WM_ACTION_GO_TO_SLEEP = 4; + + JNIEnv* env = threadEnv(); + + bool isScreenOn = this->isScreenOn(); + bool isScreenBright = this->isScreenBright(); + + jint wmActions = env->CallIntMethod(mCallbacks, gCallbacksClassInfo.hackInterceptKey, + deviceId, EV_KEY, scanCode, keyCode, policyFlags, down ? 1 : 0, when, isScreenOn); + if (checkException(env, "hackInterceptKey")) { + wmActions = 0; + } + + int32_t actions = ACTION_NONE; + if (! isScreenOn) { + // Key presses and releases wake the device. + actions |= ACTION_WOKE_HERE; + } + + if (! isScreenBright) { + // Key presses and releases brighten the screen if dimmed. + actions |= ACTION_BRIGHT_HERE; + } + + if (wmActions & WM_ACTION_GO_TO_SLEEP) { + env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.goToSleep, when); + checkException(env, "goToSleep"); + } + + if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) { + env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.pokeUserActivityForKey, when); + checkException(env, "pokeUserActivityForKey"); + } + + if (wmActions & WM_ACTION_PASS_TO_USER) { + actions |= ACTION_DISPATCH; + } + + if (! (wmActions & WM_ACTION_PASS_TO_USER)) { + if (down && isAppSwitchKey(keyCode)) { + env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.notifyAppSwitchComing); + checkException(env, "notifyAppSwitchComing"); + + actions |= ACTION_APP_SWITCH_COMING; + } + } + return actions; +} + +int32_t InputDispatchPolicy::interceptTouch(nsecs_t when) { + if (! isScreenOn()) { + // Touch events do not wake the device. + return ACTION_NONE; + } + + return ACTION_DISPATCH; +} + +int32_t InputDispatchPolicy::interceptTrackball(nsecs_t when, + bool buttonChanged, bool buttonDown, bool rolled) { + if (! isScreenOn()) { + // Trackball motions and button presses do not wake the device. + return ACTION_NONE; + } + + return ACTION_DISPATCH; +} + +bool InputDispatchPolicy::filterTouchEvents() { + if (mFilterTouchEvents < 0) { + JNIEnv* env = threadEnv(); + + jboolean result = env->CallBooleanMethod(mCallbacks, + gCallbacksClassInfo.filterTouchEvents); + if (checkException(env, "filterTouchEvents")) { + result = false; + } + + mFilterTouchEvents = result ? 1 : 0; + } + return mFilterTouchEvents; +} + +bool InputDispatchPolicy::filterJumpyTouchEvents() { + if (mFilterJumpyTouchEvents < 0) { + JNIEnv* env = threadEnv(); + + jboolean result = env->CallBooleanMethod(mCallbacks, + gCallbacksClassInfo.filterJumpyTouchEvents); + if (checkException(env, "filterJumpyTouchEvents")) { + result = false; + } + + mFilterJumpyTouchEvents = result ? 1 : 0; + } + return mFilterJumpyTouchEvents; +} + +void InputDispatchPolicy::getVirtualKeyDefinitions(const String8& deviceName, + Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) { + JNIEnv* env = threadEnv(); + + jstring deviceNameStr = env->NewStringUTF(deviceName.string()); + if (! checkException(env, "getVirtualKeyDefinitions")) { + jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks, + gCallbacksClassInfo.getVirtualKeyDefinitions, deviceNameStr)); + if (! checkException(env, "getVirtualKeyDefinitions") && result) { + jsize length = env->GetArrayLength(result); + for (jsize i = 0; i < length; i++) { + jobject item = env->GetObjectArrayElement(result, i); + + outVirtualKeyDefinitions.add(); + outVirtualKeyDefinitions.editTop().scanCode = + int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.scanCode)); + outVirtualKeyDefinitions.editTop().centerX = + int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerX)); + outVirtualKeyDefinitions.editTop().centerY = + int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerY)); + outVirtualKeyDefinitions.editTop().width = + int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.width)); + outVirtualKeyDefinitions.editTop().height = + int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.height)); + + env->DeleteLocalRef(item); + } + env->DeleteLocalRef(result); + } + env->DeleteLocalRef(deviceNameStr); + } +} + +void InputDispatchPolicy::getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) { + JNIEnv* env = threadEnv(); + + jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks, + gCallbacksClassInfo.getExcludedDeviceNames)); + if (! checkException(env, "getExcludedDeviceNames") && result) { + jsize length = env->GetArrayLength(result); + for (jsize i = 0; i < length; i++) { + jstring item = jstring(env->GetObjectArrayElement(result, i)); + + const char* deviceNameChars = env->GetStringUTFChars(item, NULL); + outExcludedDeviceNames.add(String8(deviceNameChars)); + env->ReleaseStringUTFChars(item, deviceNameChars); + + env->DeleteLocalRef(item); + } + env->DeleteLocalRef(result); + } +} + +bool InputDispatchPolicy::allowKeyRepeat() { + // Disable key repeat when the screen is off. + return isScreenOn(); +} + +nsecs_t InputDispatchPolicy::getKeyRepeatTimeout() { + // TODO use ViewConfiguration.getLongPressTimeout() + return milliseconds_to_nanoseconds(500); +} + +void InputDispatchPolicy::getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, + Vector<InputTarget>& outTargets) { + JNIEnv* env = threadEnv(); + + jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent); + if (! keyEventObj) { + LOGE("Could not obtain DVM KeyEvent object to get key event targets."); + } else { + jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks, + gCallbacksClassInfo.getKeyEventTargets, + keyEventObj, jint(keyEvent->getNature()), jint(policyFlags))); + if (! checkException(env, "getKeyEventTargets") && result) { + jsize length = env->GetArrayLength(result); + for (jsize i = 0; i < length; i++) { + jobject item = env->GetObjectArrayElement(result, i); + if (! item) { + break; // found null element indicating end of used portion of the array + } + + outTargets.add(); + android_view_InputTarget_toNative(env, item, & outTargets.editTop()); + + env->DeleteLocalRef(item); + } + env->DeleteLocalRef(result); + } + env->DeleteLocalRef(keyEventObj); + } +} + +void InputDispatchPolicy::getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, + Vector<InputTarget>& outTargets) { + JNIEnv* env = threadEnv(); + + jobject motionEventObj = android_view_MotionEvent_fromNative(env, motionEvent); + if (! motionEventObj) { + LOGE("Could not obtain DVM MotionEvent object to get key event targets."); + } else { + jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks, + gCallbacksClassInfo.getMotionEventTargets, + motionEventObj, jint(motionEvent->getNature()), jint(policyFlags))); + if (! checkException(env, "getMotionEventTargets") && result) { + jsize length = env->GetArrayLength(result); + for (jsize i = 0; i < length; i++) { + jobject item = env->GetObjectArrayElement(result, i); + if (! item) { + break; // found null element indicating end of used portion of the array + } + + outTargets.add(); + android_view_InputTarget_toNative(env, item, & outTargets.editTop()); + + env->DeleteLocalRef(item); + } + env->DeleteLocalRef(result); + } + android_view_MotionEvent_recycle(env, motionEventObj); + env->DeleteLocalRef(motionEventObj); + } +} + +} /* namespace android */ diff --git a/services/jni/com_android_server_KeyInputQueue.cpp b/services/jni/com_android_server_KeyInputQueue.cpp index c92f8df..f9e3585 100644 --- a/services/jni/com_android_server_KeyInputQueue.cpp +++ b/services/jni/com_android_server_KeyInputQueue.cpp @@ -156,7 +156,7 @@ android_server_KeyInputQueue_getSwitchState(JNIEnv* env, jobject clazz, { jint st = -1; gLock.lock(); - if (gHub != NULL) st = gHub->getSwitchState(sw); + if (gHub != NULL) st = gHub->getSwitchState(-1, -1, sw); gLock.unlock(); return st; @@ -168,7 +168,7 @@ android_server_KeyInputQueue_getSwitchStateDevice(JNIEnv* env, jobject clazz, { jint st = -1; gLock.lock(); - if (gHub != NULL) st = gHub->getSwitchState(deviceId, sw); + if (gHub != NULL) st = gHub->getSwitchState(deviceId, -1, sw); gLock.unlock(); return st; @@ -180,7 +180,7 @@ android_server_KeyInputQueue_getScancodeState(JNIEnv* env, jobject clazz, { jint st = -1; gLock.lock(); - if (gHub != NULL) st = gHub->getScancodeState(sw); + if (gHub != NULL) st = gHub->getScanCodeState(0, -1, sw); gLock.unlock(); return st; @@ -192,7 +192,7 @@ android_server_KeyInputQueue_getScancodeStateDevice(JNIEnv* env, jobject clazz, { jint st = -1; gLock.lock(); - if (gHub != NULL) st = gHub->getScancodeState(deviceId, sw); + if (gHub != NULL) st = gHub->getScanCodeState(deviceId, -1, sw); gLock.unlock(); return st; @@ -204,7 +204,7 @@ android_server_KeyInputQueue_getKeycodeState(JNIEnv* env, jobject clazz, { jint st = -1; gLock.lock(); - if (gHub != NULL) st = gHub->getKeycodeState(sw); + if (gHub != NULL) st = gHub->getKeyCodeState(0, -1, sw); gLock.unlock(); return st; @@ -216,7 +216,7 @@ android_server_KeyInputQueue_getKeycodeStateDevice(JNIEnv* env, jobject clazz, { jint st = -1; gLock.lock(); - if (gHub != NULL) st = gHub->getKeycodeState(deviceId, sw); + if (gHub != NULL) st = gHub->getKeyCodeState(deviceId,-1, sw); gLock.unlock(); return st; @@ -247,7 +247,7 @@ android_server_KeyInputQueue_hasKeys(JNIEnv* env, jobject clazz, int32_t* codes = env->GetIntArrayElements(keyCodes, NULL); uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL); - size_t numCodes = env->GetArrayLength(keyCodes); + jsize numCodes = env->GetArrayLength(keyCodes); if (numCodes == env->GetArrayLength(outFlags)) { gLock.lock(); if (gHub != NULL) ret = gHub->hasKeys(numCodes, codes, flags); diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp index d11e7e1..a1a6838 100644 --- a/services/jni/onload.cpp +++ b/services/jni/onload.cpp @@ -7,6 +7,7 @@ namespace android { int register_android_server_AlarmManagerService(JNIEnv* env); int register_android_server_BatteryService(JNIEnv* env); int register_android_server_KeyInputQueue(JNIEnv* env); +int register_android_server_InputManager(JNIEnv* env); int register_android_server_LightsService(JNIEnv* env); int register_android_server_SensorService(JNIEnv* env); int register_android_server_VibratorService(JNIEnv* env); @@ -28,6 +29,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) LOG_ASSERT(env, "Could not retrieve the env!"); register_android_server_KeyInputQueue(env); + register_android_server_InputManager(env); register_android_server_LightsService(env); register_android_server_AlarmManagerService(env); register_android_server_BatteryService(env); |