diff options
75 files changed, 12237 insertions, 749 deletions
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index d394a46..df30c76 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -16,13 +16,11 @@ package android.os; -import java.util.ArrayList; - import android.util.AndroidRuntimeException; import android.util.Config; import android.util.Log; -import com.android.internal.os.RuntimeInit; +import java.util.ArrayList; /** * Low-level class holding the list of messages to be dispatched by a @@ -34,11 +32,18 @@ import com.android.internal.os.RuntimeInit; */ public class MessageQueue { Message mMessages; - private final ArrayList mIdleHandlers = new ArrayList(); + private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); private boolean mQuiting = false; - private int mObject = 0; // used by native code boolean mQuitAllowed = true; + @SuppressWarnings("unused") + private int mPtr; // used by native code + + private native void nativeInit(); + private native void nativeDestroy(); + private native boolean nativePollOnce(int timeoutMillis); + private native void nativeWake(); + /** * Callback interface for discovering when a thread is going to block * waiting for more messages. @@ -85,55 +90,39 @@ public class MessageQueue { mIdleHandlers.remove(handler); } } - - // Add an input pipe to the set being selected over. If token is - // negative, remove 'handler's entry from the current set and forget - // about it. - void setInputToken(int token, int region, Handler handler) { - if (token >= 0) nativeRegisterInputStream(token, region, handler); - else nativeUnregisterInputStream(token); - } - + MessageQueue() { nativeInit(); } - private native void nativeInit(); - - /** - * @param token fd of the readable end of the input stream - * @param region fd of the ashmem region used for data transport alongside the 'token' fd - * @param handler Handler from which to make input messages based on data read from the fd - */ - private native void nativeRegisterInputStream(int token, int region, Handler handler); - private native void nativeUnregisterInputStream(int token); - private native void nativeSignal(); - - /** - * Wait until the designated time for new messages to arrive. - * - * @param when Timestamp in SystemClock.uptimeMillis() base of the next message in the queue. - * If 'when' is zero, the method will check for incoming messages without blocking. If - * 'when' is negative, the method will block forever waiting for the next message. - * @return - */ - private native int nativeWaitForNext(long when); + + @Override + protected void finalize() throws Throwable { + try { + nativeDestroy(); + } finally { + super.finalize(); + } + } final Message next() { boolean tryIdle = true; // when we start out, we'll just touch the input pipes and then go from there - long timeToNextEventMillis = 0; + int timeToNextEventMillis = 0; while (true) { long now; Object[] idlers = null; - nativeWaitForNext(timeToNextEventMillis); + boolean dispatched = nativePollOnce(timeToNextEventMillis); // Try to retrieve the next message, returning if found. synchronized (this) { now = SystemClock.uptimeMillis(); Message msg = pullNextLocked(now); - if (msg != null) return msg; + if (msg != null) { + return msg; + } + if (tryIdle && mIdleHandlers.size() > 0) { idlers = mIdleHandlers.toArray(); } @@ -170,9 +159,14 @@ public class MessageQueue { synchronized (this) { // No messages, nobody to tell about it... time to wait! if (mMessages != null) { - if (mMessages.when - now > 0) { + long longTimeToNextEventMillis = mMessages.when - now; + + if (longTimeToNextEventMillis > 0) { Binder.flushPendingCommands(); - timeToNextEventMillis = mMessages.when - now; + timeToNextEventMillis = (int) Math.min(longTimeToNextEventMillis, + Integer.MAX_VALUE); + } else { + timeToNextEventMillis = 0; } } else { Binder.flushPendingCommands(); @@ -230,7 +224,7 @@ public class MessageQueue { msg.next = prev.next; prev.next = msg; } - nativeSignal(); + nativeWake(); } return true; } @@ -351,7 +345,7 @@ public class MessageQueue { void poke() { synchronized (this) { - nativeSignal(); + nativeWake(); } } } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 2ade44e..3f5d6ca 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -18,6 +18,7 @@ package android.service.wallpaper; import com.android.internal.os.HandlerCaller; import com.android.internal.view.BaseIWindow; +import com.android.internal.view.BaseInputHandler; import com.android.internal.view.BaseSurfaceHolder; import android.annotation.SdkConstant; @@ -39,6 +40,10 @@ import android.util.Log; import android.util.LogPrinter; import android.view.Gravity; import android.view.IWindowSession; +import android.view.InputChannel; +import android.view.InputHandler; +import android.view.InputQueue; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; @@ -46,6 +51,7 @@ import android.view.ViewGroup; import android.view.ViewRoot; import android.view.WindowManager; import android.view.WindowManagerImpl; +import android.view.WindowManagerPolicy; import java.util.ArrayList; @@ -146,6 +152,7 @@ public abstract class WallpaperService extends Service { final WindowManager.LayoutParams mLayout = new WindowManager.LayoutParams(); IWindowSession mSession; + InputChannel mInputChannel; final Object mLock = new Object(); boolean mOffsetMessageEnqueued; @@ -205,6 +212,30 @@ public abstract class WallpaperService extends Service { }; + final InputHandler mInputHandler = new BaseInputHandler() { + @Override + public void handleTouch(MotionEvent event, Runnable finishedCallback) { + try { + synchronized (mLock) { + if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (mPendingMove != null) { + mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove); + mPendingMove.recycle(); + } + mPendingMove = event; + } else { + mPendingMove = null; + } + Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, + event); + mCaller.sendMessage(msg); + } + } finally { + finishedCallback.run(); + } + } + }; + final BaseIWindow mWindow = new BaseIWindow() { @Override public boolean onDispatchPointer(MotionEvent event, long eventTime, @@ -487,8 +518,15 @@ public abstract class WallpaperService extends Service { mLayout.setTitle(WallpaperService.this.getClass().getName()); mLayout.windowAnimations = com.android.internal.R.style.Animation_Wallpaper; - mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets); + mInputChannel = new InputChannel(); + mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets, + mInputChannel); mCreated = true; + + if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { + InputQueue.registerInputChannel(mInputChannel, mInputHandler, + Looper.myQueue()); + } } mSurfaceHolder.mSurfaceLock.lock(); @@ -587,6 +625,7 @@ public abstract class WallpaperService extends Service { mSurfaceHolder.setSizeFromLayout(); mInitializing = true; mSession = ViewRoot.getWindowSession(getMainLooper()); + mWindow.setSession(mSession); IntentFilter filter = new IntentFilter(); @@ -730,6 +769,15 @@ public abstract class WallpaperService extends Service { try { if (DEBUG) Log.v(TAG, "Removing window and destroying surface " + mSurfaceHolder.getSurface() + " of: " + this); + + if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { + if (mInputChannel != null) { + InputQueue.unregisterInputChannel(mInputChannel); + mInputChannel.dispose(); + mInputChannel = null; + } + } + mSession.remove(mWindow); } catch (RemoteException e) { } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 01f07d6..4647fb4 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -21,6 +21,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; +import android.view.InputChannel; import android.view.IWindow; import android.view.MotionEvent; import android.view.WindowManager; @@ -33,6 +34,9 @@ import android.view.Surface; */ interface IWindowSession { int add(IWindow window, in WindowManager.LayoutParams attrs, + in int viewVisibility, out Rect outContentInsets, + out InputChannel outInputChannel); + int addWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, out Rect outContentInsets); void remove(IWindow window); diff --git a/core/java/android/view/InputChannel.aidl b/core/java/android/view/InputChannel.aidl new file mode 100644 index 0000000..74c0aa4 --- /dev/null +++ b/core/java/android/view/InputChannel.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android/view/InputChannel.aidl +** +** Copyright 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; + +parcelable InputChannel; diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java new file mode 100644 index 0000000..66a83b8 --- /dev/null +++ b/core/java/android/view/InputChannel.java @@ -0,0 +1,152 @@ +/* + * 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; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Slog; + +/** + * An input channel specifies the file descriptors used to send input events to + * a window in another process. It is Parcelable so that it can be transmitted + * to the ViewRoot through a Binder transaction as part of registering the Window. + * @hide + */ +public class InputChannel implements Parcelable { + private static final String TAG = "InputChannel"; + + public static final Parcelable.Creator<InputChannel> CREATOR + = new Parcelable.Creator<InputChannel>() { + public InputChannel createFromParcel(Parcel source) { + InputChannel result = new InputChannel(); + result.readFromParcel(source); + return result; + } + + public InputChannel[] newArray(int size) { + return new InputChannel[size]; + } + }; + + @SuppressWarnings("unused") + private int mPtr; // used by native code + + private boolean mDisposeAfterWriteToParcel; + + private static native InputChannel[] nativeOpenInputChannelPair(String name); + + private native void nativeDispose(boolean finalized); + private native void nativeTransferTo(InputChannel other); + private native void nativeReadFromParcel(Parcel parcel); + private native void nativeWriteToParcel(Parcel parcel); + + private native String nativeGetName(); + + /** + * Creates an uninitialized input channel. + * It can be initialized by reading from a Parcel or by transferring the state of + * another input channel into this one. + */ + public InputChannel() { + } + + @Override + protected void finalize() throws Throwable { + try { + nativeDispose(true); + } finally { + super.finalize(); + } + } + + /** + * Creates a new input channel pair. One channel should be provided to the input + * dispatcher and the other to the application's input queue. + * @param name The descriptive (non-unique) name of the channel pair. + * @return A pair of input channels. They are symmetric and indistinguishable. + */ + public static InputChannel[] openInputChannelPair(String name) { + if (name == null) { + throw new IllegalArgumentException("name must not be null"); + } + + Slog.d(TAG, "Opening input channel pair '" + name + "'"); + return nativeOpenInputChannelPair(name); + } + + /** + * Gets the name of the input channel. + * @return The input channel name. + */ + public String getName() { + String name = nativeGetName(); + return name != null ? name : "uninitialized"; + } + + /** + * Disposes the input channel. + * Explicitly releases the reference this object is holding on the input channel. + * When all references are released, the input channel will be closed. + */ + public void dispose() { + nativeDispose(false); + } + + /** + * Transfers ownership of the internal state of the input channel to another + * instance and invalidates this instance. This is used to pass an input channel + * as an out parameter in a binder call. + * @param other The other input channel instance. + */ + public void transferToBinderOutParameter(InputChannel outParameter) { + if (outParameter == null) { + throw new IllegalArgumentException("outParameter must not be null"); + } + + nativeTransferTo(outParameter); + outParameter.mDisposeAfterWriteToParcel = true; + } + + public int describeContents() { + return Parcelable.CONTENTS_FILE_DESCRIPTOR; + } + + public void readFromParcel(Parcel in) { + if (in == null) { + throw new IllegalArgumentException("in must not be null"); + } + + nativeReadFromParcel(in); + } + + public void writeToParcel(Parcel out, int flags) { + if (out == null) { + throw new IllegalArgumentException("out must not be null"); + } + + nativeWriteToParcel(out); + + if (mDisposeAfterWriteToParcel) { + dispose(); + } + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/core/java/android/view/InputHandler.java b/core/java/android/view/InputHandler.java new file mode 100644 index 0000000..816f622 --- /dev/null +++ b/core/java/android/view/InputHandler.java @@ -0,0 +1,53 @@ +/* + * 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; + +/** + * Handles input messages that arrive on an input channel. + * @hide + */ +public interface InputHandler { + /** + * Handle a key event. + * It is the responsibility of the callee to ensure that the finished callback is + * eventually invoked when the event processing is finished and the input system + * can send the next event. + * @param event The key event data. + * @param finishedCallback The callback to invoke when event processing is finished. + */ + public void handleKey(KeyEvent event, Runnable finishedCallback); + + /** + * Handle a touch event. + * It is the responsibility of the callee to ensure that the finished callback is + * eventually invoked when the event processing is finished and the input system + * can send the next event. + * @param event The motion event data. + * @param finishedCallback The callback to invoke when event processing is finished. + */ + public void handleTouch(MotionEvent event, Runnable finishedCallback); + + /** + * Handle a trackball event. + * It is the responsibility of the callee to ensure that the finished callback is + * eventually invoked when the event processing is finished and the input system + * can send the next event. + * @param event The motion event data. + * @param finishedCallback The callback to invoke when event processing is finished. + */ + public void handleTrackball(MotionEvent event, Runnable finishedCallback); +} diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java new file mode 100644 index 0000000..b38f7d5 --- /dev/null +++ b/core/java/android/view/InputQueue.java @@ -0,0 +1,126 @@ +/* + * 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; + +import android.os.MessageQueue; +import android.util.Slog; + +/** + * An input queue provides a mechanism for an application to receive incoming + * input events sent over an input channel. Signalling is implemented by MessageQueue. + * @hide + */ +public final class InputQueue { + private static final String TAG = "InputQueue"; + + // Describes the interpretation of an event. + // XXX This concept is tentative. See comments in android/input.h. + public static final int INPUT_EVENT_NATURE_KEY = 1; + public static final int INPUT_EVENT_NATURE_TOUCH = 2; + public static final int INPUT_EVENT_NATURE_TRACKBALL = 3; + + private static Object sLock = new Object(); + + private static native void nativeRegisterInputChannel(InputChannel inputChannel, + InputHandler inputHandler, MessageQueue messageQueue); + private static native void nativeUnregisterInputChannel(InputChannel inputChannel); + private static native void nativeFinished(long finishedToken); + + private InputQueue() { + } + + /** + * Registers an input channel and handler. + * @param inputChannel The input channel to register. + * @param inputHandler The input handler to input events send to the target. + * @param messageQueue The message queue on whose thread the handler should be invoked. + */ + public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler, + MessageQueue messageQueue) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null"); + } + if (inputHandler == null) { + throw new IllegalArgumentException("inputHandler must not be null"); + } + if (messageQueue == null) { + throw new IllegalArgumentException("messageQueue must not be null"); + } + + synchronized (sLock) { + Slog.d(TAG, "Registering input channel '" + inputChannel + "'"); + nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue); + } + } + + /** + * Unregisters an input channel. + * Does nothing if the channel is not currently registered. + * @param inputChannel The input channel to unregister. + */ + public static void unregisterInputChannel(InputChannel inputChannel) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null"); + } + + synchronized (sLock) { + Slog.d(TAG, "Unregistering input channel '" + inputChannel + "'"); + nativeUnregisterInputChannel(inputChannel); + } + } + + @SuppressWarnings("unused") + private static void dispatchKeyEvent(InputHandler inputHandler, + KeyEvent event, int nature, long finishedToken) { + Runnable finishedCallback = new FinishedCallback(finishedToken); + + if (nature == INPUT_EVENT_NATURE_KEY) { + inputHandler.handleKey(event, finishedCallback); + } else { + Slog.d(TAG, "Unsupported nature for key event: " + nature); + } + } + + @SuppressWarnings("unused") + private static void dispatchMotionEvent(InputHandler inputHandler, + MotionEvent event, int nature, long finishedToken) { + Runnable finishedCallback = new FinishedCallback(finishedToken); + + if (nature == INPUT_EVENT_NATURE_TOUCH) { + inputHandler.handleTouch(event, finishedCallback); + } else if (nature == INPUT_EVENT_NATURE_TRACKBALL) { + inputHandler.handleTrackball(event, finishedCallback); + } else { + Slog.d(TAG, "Unsupported nature for motion event: " + nature); + } + } + + // TODO consider recycling finished callbacks when done + private static class FinishedCallback implements Runnable { + private long mFinishedToken; + + public FinishedCallback(long finishedToken) { + mFinishedToken = finishedToken; + } + + public void run() { + synchronized (sLock) { + nativeFinished(mFinishedToken); + } + } + } +} diff --git a/core/java/android/view/InputTarget.java b/core/java/android/view/InputTarget.java new file mode 100644 index 0000000..e56e03c --- /dev/null +++ b/core/java/android/view/InputTarget.java @@ -0,0 +1,57 @@ +/* + * 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 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/KeyEvent.java b/core/java/android/view/KeyEvent.java index 9aa16b5..ae9746e 100755 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -127,6 +127,7 @@ public class KeyEvent implements Parcelable { // NOTE: If you add a new keycode here you must also add it to: // isSystem() + // native/include/android/keycodes.h // frameworks/base/include/ui/KeycodeLabels.h // tools/puppet_master/PuppetMaster/nav_keys.py // frameworks/base/core/res/res/values/attrs.xml @@ -162,7 +163,7 @@ public class KeyEvent implements Parcelable { * key code is not {#link {@link #KEYCODE_UNKNOWN} then the * {#link {@link #getRepeatCount()} method returns the number of times * the given key code should be executed. - * Otherwise, if the key code {@link #KEYCODE_UNKNOWN}, then + * Otherwise, if the key code is {@link #KEYCODE_UNKNOWN}, then * this is a sequence of characters as returned by {@link #getCharacters}. */ public static final int ACTION_MULTIPLE = 2; @@ -330,7 +331,7 @@ public class KeyEvent implements Parcelable { private int mMetaState; private int mAction; private int mKeyCode; - private int mScancode; + private int mScanCode; private int mRepeatCount; private int mDeviceId; private int mFlags; @@ -480,7 +481,7 @@ public class KeyEvent implements Parcelable { mRepeatCount = repeat; mMetaState = metaState; mDeviceId = device; - mScancode = scancode; + mScanCode = scancode; } /** @@ -510,7 +511,7 @@ public class KeyEvent implements Parcelable { mRepeatCount = repeat; mMetaState = metaState; mDeviceId = device; - mScancode = scancode; + mScanCode = scancode; mFlags = flags; } @@ -547,7 +548,7 @@ public class KeyEvent implements Parcelable { mRepeatCount = origEvent.mRepeatCount; mMetaState = origEvent.mMetaState; mDeviceId = origEvent.mDeviceId; - mScancode = origEvent.mScancode; + mScanCode = origEvent.mScanCode; mFlags = origEvent.mFlags; mCharacters = origEvent.mCharacters; } @@ -572,7 +573,7 @@ public class KeyEvent implements Parcelable { mRepeatCount = newRepeat; mMetaState = origEvent.mMetaState; mDeviceId = origEvent.mDeviceId; - mScancode = origEvent.mScancode; + mScanCode = origEvent.mScanCode; mFlags = origEvent.mFlags; mCharacters = origEvent.mCharacters; } @@ -625,7 +626,7 @@ public class KeyEvent implements Parcelable { mRepeatCount = origEvent.mRepeatCount; mMetaState = origEvent.mMetaState; mDeviceId = origEvent.mDeviceId; - mScancode = origEvent.mScancode; + mScanCode = origEvent.mScanCode; mFlags = origEvent.mFlags; // Don't copy mCharacters, since one way or the other we'll lose it // when changing the action. @@ -859,7 +860,7 @@ public class KeyEvent implements Parcelable { * Mostly this is here for debugging purposes. */ public final int getScanCode() { - return mScancode; + return mScanCode; } /** @@ -1183,7 +1184,7 @@ public class KeyEvent implements Parcelable { public String toString() { return "KeyEvent{action=" + mAction + " code=" + mKeyCode + " repeat=" + mRepeatCount - + " meta=" + mMetaState + " scancode=" + mScancode + + " meta=" + mMetaState + " scancode=" + mScanCode + " mFlags=" + mFlags + "}"; } @@ -1208,7 +1209,7 @@ public class KeyEvent implements Parcelable { out.writeInt(mRepeatCount); out.writeInt(mMetaState); out.writeInt(mDeviceId); - out.writeInt(mScancode); + out.writeInt(mScanCode); out.writeInt(mFlags); out.writeLong(mDownTime); out.writeLong(mEventTime); @@ -1220,7 +1221,7 @@ public class KeyEvent implements Parcelable { mRepeatCount = in.readInt(); mMetaState = in.readInt(); mDeviceId = in.readInt(); - mScancode = in.readInt(); + mScanCode = in.readInt(); mFlags = in.readInt(); mDownTime = in.readLong(); mEventTime = in.readLong(); diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index eefbf7a..1f06191 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -248,17 +248,17 @@ public final class MotionEvent implements Parcelable { private RuntimeException mRecycledLocation; private boolean mRecycled; - private MotionEvent() { - mPointerIdentifiers = new int[BASE_AVAIL_POINTERS]; - mDataSamples = new float[BASE_AVAIL_POINTERS*BASE_AVAIL_SAMPLES*NUM_SAMPLE_DATA]; - mTimeSamples = new long[BASE_AVAIL_SAMPLES]; + private MotionEvent(int pointerCount, int sampleCount) { + mPointerIdentifiers = new int[pointerCount]; + mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA]; + mTimeSamples = new long[sampleCount]; } static private MotionEvent obtain() { final MotionEvent ev; synchronized (gRecyclerLock) { if (gRecyclerTop == null) { - return new MotionEvent(); + return new MotionEvent(BASE_AVAIL_POINTERS, BASE_AVAIL_SAMPLES); } ev = gRecyclerTop; gRecyclerTop = ev.mNext; @@ -269,6 +269,45 @@ public final class MotionEvent implements Parcelable { ev.mNext = null; return ev; } + + @SuppressWarnings("unused") // used by native code + static private MotionEvent obtain(int pointerCount, int sampleCount) { + final MotionEvent ev; + synchronized (gRecyclerLock) { + if (gRecyclerTop == null) { + if (pointerCount < BASE_AVAIL_POINTERS) { + pointerCount = BASE_AVAIL_POINTERS; + } + if (sampleCount < BASE_AVAIL_SAMPLES) { + sampleCount = BASE_AVAIL_SAMPLES; + } + return new MotionEvent(pointerCount, sampleCount); + } + ev = gRecyclerTop; + gRecyclerTop = ev.mNext; + gRecyclerUsed--; + } + ev.mRecycledLocation = null; + ev.mRecycled = false; + ev.mNext = null; + + if (ev.mPointerIdentifiers.length < pointerCount) { + ev.mPointerIdentifiers = new int[pointerCount]; + } + + final int timeSamplesLength = ev.mTimeSamples.length; + if (timeSamplesLength < sampleCount) { + ev.mTimeSamples = new long[sampleCount]; + } + + final int dataSamplesLength = ev.mDataSamples.length; + final int neededDataSamplesLength = pointerCount * sampleCount * NUM_SAMPLE_DATA; + if (dataSamplesLength < neededDataSamplesLength) { + ev.mDataSamples = new float[neededDataSamplesLength]; + } + + return ev; + } /** * Create a new MotionEvent, filling in all of the basic values that @@ -1022,7 +1061,7 @@ public final class MotionEvent implements Parcelable { } /** - * Returns a bitfield indicating which edges, if any, where touched by this + * Returns a bitfield indicating which edges, if any, were touched by this * MotionEvent. For touch events, clients can use this to determine if the * user's finger was touching the edge of the display. * diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 0f0cf60..8beceec 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -471,7 +471,7 @@ public class SurfaceView extends View { mWindow = new MyWindow(this); mLayout.type = mWindowType; mLayout.gravity = Gravity.LEFT|Gravity.TOP; - mSession.add(mWindow, mLayout, + mSession.addWithoutInputChannel(mWindow, mLayout, mVisible ? VISIBLE : GONE, mContentInsets); } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index aa124e6..a41c690 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -153,6 +153,7 @@ public final class ViewRoot extends Handler implements ViewParent, CompatibilityInfo.Translator mTranslator; final View.AttachInfo mAttachInfo; + InputChannel mInputChannel; final Rect mTempRect; // used in the transaction to not thrash the heap. final Rect mVisRect; // used to retrieve visible rect of focused view. @@ -219,7 +220,7 @@ public final class ViewRoot extends Handler implements ViewParent, AudioManager mAudioManager; private final int mDensity; - + public static IWindowSession getWindowSession(Looper mainLooper) { synchronized (mStaticInit) { if (!mInitialized) { @@ -434,9 +435,6 @@ public final class ViewRoot extends Handler implements ViewParent, } } - // fd [0] is the receiver, [1] is the sender - private native int[] makeInputChannel(); - /** * We have one child */ @@ -488,25 +486,20 @@ public final class ViewRoot extends Handler implements ViewParent, mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ - // Set up the input event channel - if (false) { - int[] fds = makeInputChannel(); - if (DEBUG_INPUT) { - Log.v(TAG, "makeInputChannel() returned " + fds); - } - } - // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); + mInputChannel = new InputChannel(); try { res = sWindowSession.add(mWindow, mWindowAttributes, - getHostVisibility(), mAttachInfo.mContentInsets); + getHostVisibility(), mAttachInfo.mContentInsets, + mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; + mInputChannel = null; unscheduleTraversals(); throw new RuntimeException("Adding window failed", e); } finally { @@ -514,7 +507,7 @@ public final class ViewRoot extends Handler implements ViewParent, attrs.restore(); } } - + if (mTranslator != null) { mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets); } @@ -560,6 +553,12 @@ public final class ViewRoot extends Handler implements ViewParent, throw new RuntimeException( "Unable to add window -- unknown error code " + res); } + + if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { + InputQueue.registerInputChannel(mInputChannel, mInputHandler, + Looper.myQueue()); + } + view.assignParent(this); mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0; mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0; @@ -1735,6 +1734,14 @@ public final class ViewRoot extends Handler implements ViewParent, } mSurface.release(); + if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { + if (mInputChannel != null) { + InputQueue.unregisterInputChannel(mInputChannel); + mInputChannel.dispose(); + mInputChannel = null; + } + } + try { sWindowSession.remove(mWindow); } catch (RemoteException e) { @@ -1841,19 +1848,16 @@ public final class ViewRoot extends Handler implements ViewParent, boolean callWhenDone = msg.arg1 != 0; if (event == null) { - try { - long timeBeforeGettingEvents; - if (MEASURE_LATENCY) { - timeBeforeGettingEvents = System.nanoTime(); - } + long timeBeforeGettingEvents; + if (MEASURE_LATENCY) { + timeBeforeGettingEvents = System.nanoTime(); + } - event = sWindowSession.getPendingPointerMove(mWindow); + event = getPendingPointerMotionEvent(); - if (MEASURE_LATENCY && event != null) { - lt.sample("9 Client got events ", System.nanoTime() - event.getEventTimeNano()); - lt.sample("8 Client getting events ", timeBeforeGettingEvents - event.getEventTimeNano()); - } - } catch (RemoteException e) { + if (MEASURE_LATENCY && event != null) { + lt.sample("9 Client got events ", System.nanoTime() - event.getEventTimeNano()); + lt.sample("8 Client getting events ", timeBeforeGettingEvents - event.getEventTimeNano()); } callWhenDone = false; } @@ -1928,14 +1932,9 @@ public final class ViewRoot extends Handler implements ViewParent, } } finally { if (callWhenDone) { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } - } - if (event != null) { - event.recycle(); + finishMotionEvent(); } + recycleMotionEvent(event); if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!"); // Let the exception fall through -- the looper will catch // it and take care of the bad app for us. @@ -2075,7 +2074,63 @@ public final class ViewRoot extends Handler implements ViewParent, } break; } } + + private void finishKeyEvent(KeyEvent event) { + if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { + if (mFinishedCallback != null) { + mFinishedCallback.run(); + mFinishedCallback = null; + } + } else { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + } + + private void finishMotionEvent() { + if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { + throw new IllegalStateException("Should not be reachable with native input dispatch."); + } + + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + private void recycleMotionEvent(MotionEvent event) { + if (event != null) { + event.recycle(); + } + } + + private MotionEvent getPendingPointerMotionEvent() { + if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { + throw new IllegalStateException("Should not be reachable with native input dispatch."); + } + + try { + return sWindowSession.getPendingPointerMove(mWindow); + } catch (RemoteException e) { + return null; + } + } + + private MotionEvent getPendingTrackballMotionEvent() { + if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) { + throw new IllegalStateException("Should not be reachable with native input dispatch."); + } + + try { + return sWindowSession.getPendingTrackballMove(mWindow); + } catch (RemoteException e) { + return null; + } + } + + /** * Something in the current window tells us we need to change the touch mode. For * example, we are not in touch mode, and the user touches the screen. @@ -2200,10 +2255,7 @@ public final class ViewRoot extends Handler implements ViewParent, private void deliverTrackballEvent(MotionEvent event, boolean callWhenDone) { if (event == null) { - try { - event = sWindowSession.getPendingTrackballMove(mWindow); - } catch (RemoteException e) { - } + event = getPendingTrackballMotionEvent(); callWhenDone = false; } @@ -2223,14 +2275,9 @@ public final class ViewRoot extends Handler implements ViewParent, } finally { if (handled) { if (callWhenDone) { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } - } - if (event != null) { - event.recycle(); + finishMotionEvent(); } + recycleMotionEvent(event); // If we reach this, we delivered a trackball event to mView and // mView consumed it. Because we will not translate the trackball // event into a key event, touch mode will not exit, so we exit @@ -2339,13 +2386,8 @@ public final class ViewRoot extends Handler implements ViewParent, } } finally { if (callWhenDone) { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } - if (event != null) { - event.recycle(); - } + finishMotionEvent(); + recycleMotionEvent(event); } // Let the exception fall through -- the looper will catch // it and take care of the bad app for us. @@ -2503,10 +2545,7 @@ public final class ViewRoot extends Handler implements ViewParent, if (sendDone) { if (LOCAL_LOGV) Log.v( "ViewRoot", "Telling window manager key is finished"); - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } + finishKeyEvent(event); } return; } @@ -2539,10 +2578,7 @@ public final class ViewRoot extends Handler implements ViewParent, } else if (sendDone) { if (LOCAL_LOGV) Log.v( "ViewRoot", "Telling window manager key is finished"); - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } + finishKeyEvent(event); } else { Log.w("ViewRoot", "handleFinishedEvent(seq=" + seq + " handled=" + handled + " ev=" + event @@ -2617,10 +2653,7 @@ public final class ViewRoot extends Handler implements ViewParent, if (sendDone) { if (LOCAL_LOGV) Log.v( "ViewRoot", "Telling window manager key is finished"); - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } + finishKeyEvent(event); } // Let the exception fall through -- the looper will catch // it and take care of the bad app for us. @@ -2798,6 +2831,53 @@ public final class ViewRoot extends Handler implements ViewParent, msg.obj = ri; sendMessage(msg); } + + private Runnable mFinishedCallback; + + private final InputHandler mInputHandler = new InputHandler() { + public void handleKey(KeyEvent event, Runnable finishedCallback) { + mFinishedCallback = finishedCallback; + + if (event.getAction() == KeyEvent.ACTION_DOWN) { + //noinspection ConstantConditions + if (false && event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) { + if (Config.LOGD) Log.d("keydisp", + "==================================================="); + if (Config.LOGD) Log.d("keydisp", "Focused view Hierarchy is:"); + debug(); + + if (Config.LOGD) Log.d("keydisp", + "==================================================="); + } + } + + Message msg = obtainMessage(DISPATCH_KEY); + msg.obj = event; + + if (LOCAL_LOGV) Log.v( + "ViewRoot", "sending key " + event + " to " + mView); + + sendMessageAtTime(msg, event.getEventTime()); + } + + public void handleTouch(MotionEvent event, Runnable finishedCallback) { + finishedCallback.run(); + + Message msg = obtainMessage(DISPATCH_POINTER); + msg.obj = event; + msg.arg1 = 0; + sendMessageAtTime(msg, event.getEventTime()); + } + + public void handleTrackball(MotionEvent event, Runnable finishedCallback) { + finishedCallback.run(); + + Message msg = obtainMessage(DISPATCH_TRACKBALL); + msg.obj = event; + msg.arg1 = 0; + sendMessageAtTime(msg, event.getEventTime()); + } + }; public void dispatchKey(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN) { @@ -2968,7 +3048,7 @@ public final class ViewRoot extends Handler implements ViewParent, } } - static class EventCompletion extends Handler { + class EventCompletion extends Handler { final IWindow mWindow; final KeyEvent mKeyEvent; final boolean mIsPointer; @@ -2987,40 +3067,25 @@ public final class ViewRoot extends Handler implements ViewParent, @Override public void handleMessage(Message msg) { if (mKeyEvent != null) { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } + finishKeyEvent(mKeyEvent); } else if (mIsPointer) { boolean didFinish; MotionEvent event = mMotionEvent; if (event == null) { - try { - event = sWindowSession.getPendingPointerMove(mWindow); - } catch (RemoteException e) { - } + event = getPendingPointerMotionEvent(); didFinish = true; } else { didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE; } if (!didFinish) { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } + finishMotionEvent(); } } else { MotionEvent event = mMotionEvent; if (event == null) { - try { - event = sWindowSession.getPendingTrackballMove(mWindow); - } catch (RemoteException e) { - } + event = getPendingTrackballMotionEvent(); } else { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } + finishMotionEvent(); } } } @@ -3050,7 +3115,7 @@ public final class ViewRoot extends Handler implements ViewParent, viewRoot.dispatchKey(event); } else { Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!"); - new EventCompletion(mMainLooper, this, event, false, null); + viewRoot.new EventCompletion(mMainLooper, this, event, false, null); } } @@ -3064,7 +3129,7 @@ public final class ViewRoot extends Handler implements ViewParent, } viewRoot.dispatchPointer(event, eventTime, callWhenDone); } else { - new EventCompletion(mMainLooper, this, null, true, event); + viewRoot.new EventCompletion(mMainLooper, this, null, true, event); } } @@ -3074,7 +3139,7 @@ public final class ViewRoot extends Handler implements ViewParent, if (viewRoot != null) { viewRoot.dispatchTrackball(event, eventTime, callWhenDone); } else { - new EventCompletion(mMainLooper, this, null, false, event); + viewRoot.new EventCompletion(mMainLooper, this, null, false, event); } } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index b39cb9d..431b786 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -78,6 +78,12 @@ public interface WindowManagerPolicy { public final static int FLAG_BRIGHT_HERE = 0x20000000; public final static boolean WATCH_POINTER = false; + + /** + * 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; // flags for interceptKeyTq /** @@ -708,6 +714,8 @@ public interface WindowManagerPolicy { */ public boolean preprocessInputEventTq(RawInputEvent event); + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen); + /** * Determine whether a given key code is used to cause an app switch * to occur (most often the HOME key, also often ENDCALL). If you return diff --git a/core/java/com/android/internal/view/BaseInputHandler.java b/core/java/com/android/internal/view/BaseInputHandler.java new file mode 100644 index 0000000..6fe5063 --- /dev/null +++ b/core/java/com/android/internal/view/BaseInputHandler.java @@ -0,0 +1,39 @@ +/* + * 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.internal.view; + +import android.view.InputHandler; +import android.view.KeyEvent; +import android.view.MotionEvent; + +/** + * Base do-nothing implementation of an input handler. + * @hide + */ +public abstract class BaseInputHandler implements InputHandler { + public void handleKey(KeyEvent event, Runnable finishedCallback) { + finishedCallback.run(); + } + + public void handleTouch(MotionEvent event, Runnable finishedCallback) { + finishedCallback.run(); + } + + public void handleTrackball(MotionEvent event, Runnable finishedCallback) { + finishedCallback.run(); + } +} diff --git a/core/jni/Android.mk b/core/jni/Android.mk index d4545d7..d854e87 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -45,6 +45,11 @@ LOCAL_SRC_FILES:= \ android_view_Display.cpp \ android_view_Surface.cpp \ 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 \ android_text_AndroidBidi.cpp \ android_text_KeyCharacterMap.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f66ed83..466642a 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -162,6 +162,11 @@ extern int register_android_backup_BackupDataOutput(JNIEnv *env); extern int register_android_backup_FileBackupHelperBase(JNIEnv *env); 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); static AndroidRuntime* gCurRuntime = NULL; @@ -1288,6 +1293,11 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_backup_BackupHelperDispatcher), 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_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp index 8984057..030d6c7 100644 --- a/core/jni/android_os_MessageQueue.cpp +++ b/core/jni/android_os_MessageQueue.cpp @@ -14,325 +14,151 @@ * limitations under the License. */ -#define LOG_TAG "MQNative" +#define LOG_TAG "MessageQueue-JNI" #include "JNIHelp.h" -#include <sys/socket.h> -#include <sys/select.h> -#include <sys/time.h> -#include <fcntl.h> - -#include <android_runtime/AndroidRuntime.h> -#include <utils/SystemClock.h> -#include <utils/Vector.h> +#include <utils/PollLoop.h> #include <utils/Log.h> +#include "android_os_MessageQueue.h" -using namespace android; +namespace android { // ---------------------------------------------------------------------------- static struct { - jclass mClass; - - jfieldID mObject; // native object attached to the DVM MessageQueue -} gMessageQueueOffsets; - -static struct { - jclass mClass; - jmethodID mConstructor; -} gKeyEventOffsets; - -// TODO: also MotionEvent offsets etc. a la gKeyEventOffsets - -static struct { - jclass mClass; - jmethodID mObtain; // obtain(Handler h, int what, Object obj) -} gMessageOffsets; - -// ---------------------------------------------------------------------------- + jclass clazz; -static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL) -{ - if (jniThrowException(env, exc, msg) != 0) - assert(false); -} + jfieldID mPtr; // native object attached to the DVM MessageQueue +} gMessageQueueClassInfo; // ---------------------------------------------------------------------------- -class MessageQueueNative { +class NativeMessageQueue { public: - MessageQueueNative(int readSocket, int writeSocket); - ~MessageQueueNative(); - - // select on all FDs until the designated time; forever if wakeupTime is < 0 - int waitForSignal(jobject mqueue, jlong wakeupTime); + NativeMessageQueue(); + ~NativeMessageQueue(); - // signal the queue-ready pipe - void signalQueuePipe(); + inline sp<PollLoop> getPollLoop() { return mPollLoop; } - // Specify a new input pipe, passing in responsibility for the socket fd and - // ashmem region - int registerInputPipe(JNIEnv* env, int socketFd, int memRegionFd, jobject handler); - - // Forget about this input pipe, closing the socket and ashmem region as well - int unregisterInputPipe(JNIEnv* env, int socketFd); - - size_t numRegisteredPipes() const { return mInputPipes.size(); } + bool pollOnce(int timeoutMillis); + void wake(); private: - struct InputPipe { - int fd; - int region; - jobject handler; - - InputPipe() {} - InputPipe(int _fd, int _r, jobject _h) : fd(_fd), region(_r), handler(_h) {} - }; - - // consume an event from a socket, put it on the DVM MessageQueue indicated, - // and notify the other end of the pipe that we've consumed it. - void queueEventFromPipe(const InputPipe& pipe, jobject mqueue); - - int mQueueReadFd, mQueueWriteFd; - Vector<InputPipe> mInputPipes; + sp<PollLoop> mPollLoop; }; -MessageQueueNative::MessageQueueNative(int readSocket, int writeSocket) - : mQueueReadFd(readSocket), mQueueWriteFd(writeSocket) { -} +// ---------------------------------------------------------------------------- -MessageQueueNative::~MessageQueueNative() { +NativeMessageQueue::NativeMessageQueue() { + mPollLoop = new PollLoop(); } -int MessageQueueNative::waitForSignal(jobject mqueue, jlong timeoutMillis) { - struct timeval tv, *timeout; - fd_set fdset; - - if (timeoutMillis < 0) { - timeout = NULL; - } else { - if (timeoutMillis == 0) { - tv.tv_sec = 0; - tv.tv_usec = 0; - } else { - tv.tv_sec = (timeoutMillis / 1000); - tv.tv_usec = (timeoutMillis - (1000 * tv.tv_sec)) * 1000; - } - timeout = &tv; - } - - // always rebuild the fd set from scratch - FD_ZERO(&fdset); - - // the queue signalling pipe - FD_SET(mQueueReadFd, &fdset); - int maxFd = mQueueReadFd; - - // and the input sockets themselves - for (size_t i = 0; i < mInputPipes.size(); i++) { - FD_SET(mInputPipes[i].fd, &fdset); - if (maxFd < mInputPipes[i].fd) { - maxFd = mInputPipes[i].fd; - } - } - - // now wait - int res = select(maxFd + 1, &fdset, NULL, NULL, timeout); - - // Error? Just return it and bail - if (res < 0) return res; - - // What happened -- timeout or actual data arrived? - if (res == 0) { - // select() returned zero, which means we timed out, which means that it's time - // to deliver the head element that was already on the queue. Just fall through - // without doing anything else. - } else { - // Data (or a queue signal) arrived! - // - // If it's data, pull the data off the pipe, build a new Message with it, put it on - // the DVM-side MessageQueue (pointed to by the 'mqueue' parameter), then proceed - // into the queue-signal case. - // - // If a queue signal arrived, just consume any data pending in that pipe and - // fall out. - bool queue_signalled = (FD_ISSET(mQueueReadFd, &fdset) != 0); - - for (size_t i = 0; i < mInputPipes.size(); i++) { - if (FD_ISSET(mInputPipes[i].fd, &fdset)) { - queueEventFromPipe(mInputPipes[i], mqueue); - queue_signalled = true; // we know a priori that queueing the event does this - } - } - - // Okay, stuff went on the queue. Consume the contents of the signal pipe - // now that we're awake and about to start dispatching messages again. - if (queue_signalled) { - uint8_t buf[16]; - ssize_t nRead; - do { - nRead = read(mQueueReadFd, buf, sizeof(buf)); - } while (nRead > 0); // in nonblocking mode we'll get -1 when it's drained - } - } - - return 0; +NativeMessageQueue::~NativeMessageQueue() { } -// signals to the queue pipe are one undefined byte. it's just a "data has arrived" token -// and the pipe is drained on receipt of at least one signal -void MessageQueueNative::signalQueuePipe() { - int dummy[1]; - write(mQueueWriteFd, dummy, 1); +bool NativeMessageQueue::pollOnce(int timeoutMillis) { + return mPollLoop->pollOnce(timeoutMillis); } -void MessageQueueNative::queueEventFromPipe(const InputPipe& inPipe, jobject mqueue) { - // !!! TODO: read the event data from the InputPipe's ashmem region, convert it to a DVM - // event object of the proper type [MotionEvent or KeyEvent], create a Message holding - // it as appropriate, point the Message to the Handler associated with this InputPipe, - // and call up to the DVM MessageQueue implementation to enqueue it for delivery. +void NativeMessageQueue::wake() { + mPollLoop->wake(); } -// the number of registered pipes on success; < 0 on error -int MessageQueueNative::registerInputPipe(JNIEnv* env, - int socketFd, int memRegionFd, jobject handler) { - // make sure this fd is not already known to us - for (size_t i = 0; i < mInputPipes.size(); i++) { - if (mInputPipes[i].fd == socketFd) { - LOGE("Attempt to re-register input fd %d", socketFd); - return -1; - } - } +// ---------------------------------------------------------------------------- - mInputPipes.push( InputPipe(socketFd, memRegionFd, env->NewGlobalRef(handler)) ); - return mInputPipes.size(); +static NativeMessageQueue* android_os_MessageQueue_getNativeMessageQueue(JNIEnv* env, + jobject messageQueueObj) { + jint intPtr = env->GetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr); + return reinterpret_cast<NativeMessageQueue*>(intPtr); } -// Remove an input pipe from our bookkeeping. Also closes the socket and ashmem -// region file descriptor! -// -// returns the number of remaining input pipes on success; < 0 on error -int MessageQueueNative::unregisterInputPipe(JNIEnv* env, int socketFd) { - for (size_t i = 0; i < mInputPipes.size(); i++) { - if (mInputPipes[i].fd == socketFd) { - close(mInputPipes[i].fd); - close(mInputPipes[i].region); - env->DeleteGlobalRef(mInputPipes[i].handler); - mInputPipes.removeAt(i); - return mInputPipes.size(); - } - } - LOGW("Tried to unregister input pipe %d but not found!", socketFd); - return -1; +static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj, + NativeMessageQueue* nativeMessageQueue) { + env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr, + reinterpret_cast<jint>(nativeMessageQueue)); } -// ---------------------------------------------------------------------------- - -namespace android { - -static void android_os_MessageQueue_init(JNIEnv* env, jobject obj) { - // Create the pipe - int fds[2]; - int err = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); - if (err != 0) { - doThrow(env, "java/lang/RuntimeException", "Unable to create socket pair"); - } +sp<PollLoop> android_os_MessageQueue_getPollLoop(JNIEnv* env, jobject messageQueueObj) { + NativeMessageQueue* nativeMessageQueue = + android_os_MessageQueue_getNativeMessageQueue(env, messageQueueObj); + return nativeMessageQueue != NULL ? nativeMessageQueue->getPollLoop() : NULL; +} - MessageQueueNative *mqn = new MessageQueueNative(fds[0], fds[1]); - if (mqn == NULL) { - close(fds[0]); - close(fds[1]); - doThrow(env, "java/lang/RuntimeException", "Unable to allocate native queue"); +static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) { + NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); + if (! nativeMessageQueue) { + jniThrowRuntimeException(env, "Unable to allocate native queue"); + return; } - int flags = fcntl(fds[0], F_GETFL); - fcntl(fds[0], F_SETFL, flags | O_NONBLOCK); - flags = fcntl(fds[1], F_GETFL); - fcntl(fds[1], F_SETFL, flags | O_NONBLOCK); - - env->SetIntField(obj, gMessageQueueOffsets.mObject, (jint)mqn); + android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue); } -static void android_os_MessageQueue_signal(JNIEnv* env, jobject obj) { - MessageQueueNative *mqn = (MessageQueueNative*) env->GetIntField(obj, gMessageQueueOffsets.mObject); - if (mqn != NULL) { - mqn->signalQueuePipe(); - } else { - doThrow(env, "java/lang/IllegalStateException", "Queue not initialized"); +static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jobject obj) { + NativeMessageQueue* nativeMessageQueue = + android_os_MessageQueue_getNativeMessageQueue(env, obj); + if (nativeMessageQueue) { + android_os_MessageQueue_setNativeMessageQueue(env, obj, NULL); + delete nativeMessageQueue; } } -static int android_os_MessageQueue_waitForNext(JNIEnv* env, jobject obj, jlong when) { - MessageQueueNative *mqn = (MessageQueueNative*) env->GetIntField(obj, gMessageQueueOffsets.mObject); - if (mqn != NULL) { - int res = mqn->waitForSignal(obj, when); - return res; // the DVM event, if any, has been constructed and queued now - } - - return -1; +static void throwQueueNotInitialized(JNIEnv* env) { + jniThrowException(env, "java/lang/IllegalStateException", "Message queue not initialized"); } -static void android_os_MessageQueue_registerInputStream(JNIEnv* env, jobject obj, - jint socketFd, jint regionFd, jobject handler) { - MessageQueueNative *mqn = (MessageQueueNative*) env->GetIntField(obj, gMessageQueueOffsets.mObject); - if (mqn != NULL) { - mqn->registerInputPipe(env, socketFd, regionFd, handler); - } else { - doThrow(env, "java/lang/IllegalStateException", "Queue not initialized"); +static jboolean android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, + jint timeoutMillis) { + NativeMessageQueue* nativeMessageQueue = + android_os_MessageQueue_getNativeMessageQueue(env, obj); + if (! nativeMessageQueue) { + throwQueueNotInitialized(env); + return false; } + return nativeMessageQueue->pollOnce(timeoutMillis); } -static void android_os_MessageQueue_unregisterInputStream(JNIEnv* env, jobject obj, - jint socketFd) { - MessageQueueNative *mqn = (MessageQueueNative*) env->GetIntField(obj, gMessageQueueOffsets.mObject); - if (mqn != NULL) { - mqn->unregisterInputPipe(env, socketFd); - } else { - doThrow(env, "java/lang/IllegalStateException", "Queue not initialized"); +static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj) { + NativeMessageQueue* nativeMessageQueue = + android_os_MessageQueue_getNativeMessageQueue(env, obj); + if (! nativeMessageQueue) { + throwQueueNotInitialized(env); + return; } + return nativeMessageQueue->wake(); } // ---------------------------------------------------------------------------- -const char* const kKeyEventPathName = "android/view/KeyEvent"; -const char* const kMessagePathName = "android/os/Message"; -const char* const kMessageQueuePathName = "android/os/MessageQueue"; - static JNINativeMethod gMessageQueueMethods[] = { /* name, signature, funcPtr */ - { "nativeInit", "()V", (void*)android_os_MessageQueue_init }, - { "nativeSignal", "()V", (void*)android_os_MessageQueue_signal }, - { "nativeWaitForNext", "(J)I", (void*)android_os_MessageQueue_waitForNext }, - { "nativeRegisterInputStream", "(IILandroid/os/Handler;)V", (void*)android_os_MessageQueue_registerInputStream }, - { "nativeUnregisterInputStream", "(I)V", (void*)android_os_MessageQueue_unregisterInputStream }, + { "nativeInit", "()V", (void*)android_os_MessageQueue_nativeInit }, + { "nativeDestroy", "()V", (void*)android_os_MessageQueue_nativeDestroy }, + { "nativePollOnce", "(I)Z", (void*)android_os_MessageQueue_nativePollOnce }, + { "nativeWake", "()V", (void*)android_os_MessageQueue_nativeWake } }; +#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_os_MessageQueue(JNIEnv* env) { - jclass clazz; + int res = jniRegisterNativeMethods(env, "android/os/MessageQueue", + gMessageQueueMethods, NELEM(gMessageQueueMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gMessageQueueClassInfo.clazz, "android/os/MessageQueue"); - clazz = env->FindClass(kMessageQueuePathName); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.MessageQueue"); - gMessageQueueOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gMessageQueueOffsets.mObject = env->GetFieldID(clazz, "mObject", "I"); - assert(gMessageQueueOffsets.mObject); - - clazz = env->FindClass(kMessagePathName); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Message"); - gMessageOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gMessageOffsets.mObtain = env->GetStaticMethodID(clazz, "obtain", - "(Landroid/os/Handler;ILjava/lang/Object;)Landroid/os/Message;"); - assert(gMessageOffsets.mObtain); - - clazz = env->FindClass(kKeyEventPathName); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.view.KeyEvent"); - gKeyEventOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gKeyEventOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(JJIIIIIII)V"); - assert(gKeyEventOffsets.mConstructor); + GET_FIELD_ID(gMessageQueueClassInfo.mPtr, gMessageQueueClassInfo.clazz, + "mPtr", "I"); - return AndroidRuntime::registerNativeMethods(env, kMessageQueuePathName, - gMessageQueueMethods, NELEM(gMessageQueueMethods)); + return 0; } - -}; // end of namespace android +} // namespace android diff --git a/core/jni/android_os_MessageQueue.h b/core/jni/android_os_MessageQueue.h new file mode 100644 index 0000000..5c742e2 --- /dev/null +++ b/core/jni/android_os_MessageQueue.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#ifndef _ANDROID_OS_MESSAGEQUEUE_H +#define _ANDROID_OS_MESSAGEQUEUE_H + +#include "jni.h" + +namespace android { + +class PollLoop; + +extern sp<PollLoop> android_os_MessageQueue_getPollLoop(JNIEnv* env, jobject messageQueueObj); + +} // namespace android + +#endif // _ANDROID_OS_MESSAGEQUEUE_H diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp new file mode 100644 index 0000000..47bb073 --- /dev/null +++ b/core/jni/android_view_InputChannel.cpp @@ -0,0 +1,288 @@ +/* + * 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 "InputChannel-JNI" + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <binder/Parcel.h> +#include <utils/Log.h> +#include <ui/InputTransport.h> +#include "android_view_InputChannel.h" +#include "android_util_Binder.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jfieldID mPtr; // native object attached to the DVM InputChannel + jmethodID ctor; +} gInputChannelClassInfo; + +// ---------------------------------------------------------------------------- + +class NativeInputChannel { +public: + NativeInputChannel(const sp<InputChannel>& inputChannel); + ~NativeInputChannel(); + + inline sp<InputChannel> getInputChannel() { return mInputChannel; } + + void setDisposeCallback(InputChannelObjDisposeCallback callback, void* data); + void invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj); + +private: + sp<InputChannel> mInputChannel; + InputChannelObjDisposeCallback mDisposeCallback; + void* mDisposeData; +}; + +// ---------------------------------------------------------------------------- + +NativeInputChannel::NativeInputChannel(const sp<InputChannel>& inputChannel) : + mInputChannel(inputChannel), mDisposeCallback(NULL) { +} + +NativeInputChannel::~NativeInputChannel() { +} + +void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) { + mDisposeCallback = callback; + mDisposeData = data; +} + +void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) { + if (mDisposeCallback) { + mDisposeCallback(env, obj, mInputChannel, mDisposeData); + mDisposeCallback = NULL; + mDisposeData = NULL; + } +} + +// ---------------------------------------------------------------------------- + +static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env, + jobject inputChannelObj) { + jint intPtr = env->GetIntField(inputChannelObj, gInputChannelClassInfo.mPtr); + return reinterpret_cast<NativeInputChannel*>(intPtr); +} + +static void android_view_InputChannel_setNativeInputChannel(JNIEnv* env, jobject inputChannelObj, + NativeInputChannel* nativeInputChannel) { + env->SetIntField(inputChannelObj, gInputChannelClassInfo.mPtr, + reinterpret_cast<jint>(nativeInputChannel)); +} + +sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, inputChannelObj); + return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL; +} + +void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj, + InputChannelObjDisposeCallback callback, void* data) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, inputChannelObj); + if (nativeInputChannel == NULL) { + LOGW("Cannot set dispose callback because input channel object has not been initialized."); + } else { + nativeInputChannel->setDisposeCallback(callback, data); + } +} + +static jobject android_view_InputChannel_createInputChannel(JNIEnv* env, + NativeInputChannel* nativeInputChannel) { + jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz, + gInputChannelClassInfo.ctor); + android_view_InputChannel_setNativeInputChannel(env, inputChannelObj, nativeInputChannel); + return inputChannelObj; +} + +static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env, + jclass clazz, jstring nameObj) { + const char* nameChars = env->GetStringUTFChars(nameObj, NULL); + String8 name(nameChars); + env->ReleaseStringUTFChars(nameObj, nameChars); + + InputChannel* serverChannel; + InputChannel* clientChannel; + status_t result = InputChannel::openInputChannelPair(name, & serverChannel, & clientChannel); + + if (result) { + LOGE("Could not open input channel pair. status=%d", result); + jniThrowRuntimeException(env, "Could not open input channel pair."); + return NULL; + } + + // TODO more robust error checking + jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, + new NativeInputChannel(serverChannel)); + jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, + new NativeInputChannel(clientChannel)); + + jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL); + env->SetObjectArrayElement(channelPair, 0, serverChannelObj); + env->SetObjectArrayElement(channelPair, 1, clientChannelObj); + return channelPair; +} + +static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jboolean finalized) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, obj); + if (nativeInputChannel) { + if (finalized) { + LOGW("Input channel object '%s' was finalized without being disposed!", + nativeInputChannel->getInputChannel()->getName().string()); + } + + nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj); + + android_view_InputChannel_setNativeInputChannel(env, obj, NULL); + delete nativeInputChannel; + } +} + +static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj, + jobject otherObj) { + if (android_view_InputChannel_getInputChannel(env, otherObj) != NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "Other object already has a native input channel."); + return; + } + + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, obj); + android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel); + android_view_InputChannel_setNativeInputChannel(env, obj, NULL); +} + +static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj, + jobject parcelObj) { + if (android_view_InputChannel_getInputChannel(env, obj) != NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "This object already has a native input channel."); + return; + } + + Parcel* parcel = parcelForJavaObject(env, parcelObj); + if (parcel) { + bool isInitialized = parcel->readInt32(); + if (isInitialized) { + String8 name = parcel->readString8(); + int32_t ashmemFd = dup(parcel->readFileDescriptor()); + int32_t receivePipeFd = dup(parcel->readFileDescriptor()); + int32_t sendPipeFd = dup(parcel->readFileDescriptor()); + if (ashmemFd < 0 || receivePipeFd < 0 || sendPipeFd < 0) { + if (ashmemFd >= 0) ::close(ashmemFd); + if (receivePipeFd >= 0) ::close(receivePipeFd); + if (sendPipeFd >= 0) ::close(sendPipeFd); + jniThrowRuntimeException(env, + "Could not read input channel file descriptors from parcel."); + return; + } + + InputChannel* inputChannel = new InputChannel(name, ashmemFd, + receivePipeFd, sendPipeFd); + NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel); + + android_view_InputChannel_setNativeInputChannel(env, obj, nativeInputChannel); + } + } +} + +static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject obj, + jobject parcelObj) { + Parcel* parcel = parcelForJavaObject(env, parcelObj); + if (parcel) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, obj); + if (nativeInputChannel) { + sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel(); + + parcel->writeInt32(1); + parcel->writeString8(inputChannel->getName()); + parcel->writeDupFileDescriptor(inputChannel->getAshmemFd()); + parcel->writeDupFileDescriptor(inputChannel->getReceivePipeFd()); + parcel->writeDupFileDescriptor(inputChannel->getSendPipeFd()); + } else { + parcel->writeInt32(0); + } + } +} + +static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, obj); + if (! nativeInputChannel) { + return NULL; + } + + jstring name = env->NewStringUTF(nativeInputChannel->getInputChannel()->getName().string()); + return name; +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gInputChannelMethods[] = { + /* name, signature, funcPtr */ + { "nativeOpenInputChannelPair", "(Ljava/lang/String;)[Landroid/view/InputChannel;", + (void*)android_view_InputChannel_nativeOpenInputChannelPair }, + { "nativeDispose", "(Z)V", + (void*)android_view_InputChannel_nativeDispose }, + { "nativeTransferTo", "(Landroid/view/InputChannel;)V", + (void*)android_view_InputChannel_nativeTransferTo }, + { "nativeReadFromParcel", "(Landroid/os/Parcel;)V", + (void*)android_view_InputChannel_nativeReadFromParcel }, + { "nativeWriteToParcel", "(Landroid/os/Parcel;)V", + (void*)android_view_InputChannel_nativeWriteToParcel }, + { "nativeGetName", "()Ljava/lang/String;", + (void*)android_view_InputChannel_nativeGetName }, +}; + +#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_view_InputChannel(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/view/InputChannel", + gInputChannelMethods, NELEM(gInputChannelMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gInputChannelClassInfo.clazz, "android/view/InputChannel"); + + GET_FIELD_ID(gInputChannelClassInfo.mPtr, gInputChannelClassInfo.clazz, + "mPtr", "I"); + + GET_METHOD_ID(gInputChannelClassInfo.ctor, gInputChannelClassInfo.clazz, + "<init>", "()V"); + + return 0; +} + +} // namespace android diff --git a/core/jni/android_view_InputChannel.h b/core/jni/android_view_InputChannel.h new file mode 100644 index 0000000..ac1defb --- /dev/null +++ b/core/jni/android_view_InputChannel.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef _ANDROID_VIEW_INPUTCHANNEL_H +#define _ANDROID_VIEW_INPUTCHANNEL_H + +#include "jni.h" + +namespace android { + +class InputChannel; + +typedef void (*InputChannelObjDisposeCallback)(JNIEnv* env, jobject inputChannelObj, + const sp<InputChannel>& inputChannel, void* data); + +extern sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, + jobject inputChannelObj); + +/* Sets a callback that is invoked when the InputChannel DVM object is disposed (or finalized). + * This is used to automatically dispose of other native objects in the input dispatcher + * and input queue to prevent memory leaks. */ +extern void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj, + InputChannelObjDisposeCallback callback, void* data = NULL); + +} // namespace android + +#endif // _ANDROID_OS_INPUTCHANNEL_H diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp new file mode 100644 index 0000000..9cbde25 --- /dev/null +++ b/core/jni/android_view_InputQueue.cpp @@ -0,0 +1,471 @@ +/* + * 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 "InputQueue-JNI" + +//#define LOG_NDEBUG 0 + +// Log debug messages about the dispatch cycle. +#define DEBUG_DISPATCH_CYCLE 1 + + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <utils/Log.h> +#include <utils/PollLoop.h> +#include <utils/KeyedVector.h> +#include <utils/threads.h> +#include <ui/InputTransport.h> +#include "android_os_MessageQueue.h" +#include "android_view_InputChannel.h" +#include "android_view_KeyEvent.h" +#include "android_view_MotionEvent.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jmethodID dispatchKeyEvent; + jmethodID dispatchMotionEvent; +} gInputQueueClassInfo; + +// ---------------------------------------------------------------------------- + +class NativeInputQueue { +public: + NativeInputQueue(); + virtual ~NativeInputQueue(); + + status_t registerInputChannel(JNIEnv* env, jobject inputChannelObj, + jobject inputHandlerObj, jobject messageQueueObj); + + status_t unregisterInputChannel(JNIEnv* env, jobject inputChannelObj); + + status_t finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish); + +private: + class Connection : public RefBase { + protected: + virtual ~Connection(); + + public: + enum Status { + // Everything is peachy. + STATUS_NORMAL, + // The input channel has been unregistered. + STATUS_ZOMBIE + }; + + Connection(const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop); + + inline const char* getInputChannelName() { return inputChannel->getName().string(); } + + Status status; + + sp<InputChannel> inputChannel; + InputConsumer inputConsumer; + sp<PollLoop> pollLoop; + jobject inputHandlerObjGlobal; + PreallocatedInputEventFactory inputEventFactory; + + // The sequence number of the current event being dispatched. + // This is used as part of the finished token as a way to determine whether the finished + // token is still valid before sending a finished signal back to the publisher. + uint32_t messageSeqNum; + + // True if a message has been received from the publisher but not yet finished. + bool messageInProgress; + }; + + Mutex mLock; + KeyedVector<int32_t, sp<Connection> > mConnectionsByReceiveFd; + + static void handleInputChannelDisposed(JNIEnv* env, + jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data); + + static bool handleReceiveCallback(int receiveFd, int events, void* data); + + static jlong generateFinishedToken(int32_t receiveFd, int32_t messageSeqNum); + + static void parseFinishedToken(jlong finishedToken, + int32_t* outReceiveFd, uint32_t* outMessageIndex); +}; + +// ---------------------------------------------------------------------------- + +NativeInputQueue::NativeInputQueue() { +} + +NativeInputQueue::~NativeInputQueue() { +} + +status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj, + jobject inputHandlerObj, jobject messageQueueObj) { + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == NULL) { + LOGW("Input channel is not initialized."); + return BAD_VALUE; + } + + sp<PollLoop> pollLoop = android_os_MessageQueue_getPollLoop(env, messageQueueObj); + + int receiveFd; + { // acquire lock + AutoMutex _l(mLock); + + receiveFd = inputChannel->getReceivePipeFd(); + if (mConnectionsByReceiveFd.indexOfKey(receiveFd) >= 0) { + LOGW("Attempted to register already registered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + sp<Connection> connection = new Connection(inputChannel, pollLoop); + status_t result = connection->inputConsumer.initialize(); + if (result) { + LOGW("Failed to initialize input consumer for input channel '%s', status=%d", + inputChannel->getName().string(), result); + return result; + } + + connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj); + + mConnectionsByReceiveFd.add(receiveFd, connection); + } // release lock + + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, + handleInputChannelDisposed, this); + + pollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, NULL); + return OK; +} + +status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChannelObj) { + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == NULL) { + LOGW("Input channel is not initialized."); + return BAD_VALUE; + } + + int32_t receiveFd; + sp<Connection> connection; + { // acquire lock + AutoMutex _l(mLock); + + receiveFd = inputChannel->getReceivePipeFd(); + ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + LOGW("Attempted to unregister already unregistered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + mConnectionsByReceiveFd.removeItemsAt(connectionIndex); + + connection->status = Connection::STATUS_ZOMBIE; + + env->DeleteGlobalRef(connection->inputHandlerObjGlobal); + connection->inputHandlerObjGlobal = NULL; + } // release lock + + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL); + + connection->pollLoop->removeCallback(receiveFd); + return OK; +} + +status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish) { + int32_t receiveFd; + uint32_t messageSeqNum; + parseFinishedToken(finishedToken, &receiveFd, &messageSeqNum); + + { // acquire lock + AutoMutex _l(mLock); + + ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + if (! ignoreSpuriousFinish) { + LOGW("Attempted to finish input on channel that is no longer registered."); + } + return DEAD_OBJECT; + } + + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + if (messageSeqNum != connection->messageSeqNum || ! connection->messageInProgress) { + if (! ignoreSpuriousFinish) { + LOGW("Attempted to finish input twice on channel '%s'.", + connection->getInputChannelName()); + } + return INVALID_OPERATION; + } + + connection->messageInProgress = false; + + status_t status = connection->inputConsumer.sendFinishedSignal(); + if (status) { + LOGW("Failed to send finished signal on channel '%s'. status=%d", + connection->getInputChannelName(), status); + return status; + } + +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Finished event.", + connection->getInputChannelName()); +#endif + } // release lock + + return OK; +} + +void NativeInputQueue::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 queue!", inputChannel->getName().string()); + + NativeInputQueue* q = static_cast<NativeInputQueue*>(data); + q->unregisterInputChannel(env, inputChannelObj); +} + +bool NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) { + NativeInputQueue* q = static_cast<NativeInputQueue*>(data); + JNIEnv* env = AndroidRuntime::getJNIEnv(); + + sp<Connection> connection; + InputEvent* inputEvent; + jobject inputHandlerObjLocal; + jlong finishedToken; + { // acquire lock + AutoMutex _l(q->mLock); + + ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + LOGE("Received spurious receive callback for unknown input channel. " + "fd=%d, events=0x%x", receiveFd, events); + return false; // remove the callback + } + + connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex); + if (events & (POLLERR | POLLHUP | POLLNVAL)) { + LOGE("channel '%s' ~ Publisher closed input channel or an error occurred. " + "events=0x%x", connection->getInputChannelName(), events); + return false; // remove the callback + } + + if (! (events & POLLIN)) { + LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " + "events=0x%x", connection->getInputChannelName(), events); + return true; + } + + status_t status = connection->inputConsumer.receiveDispatchSignal(); + if (status) { + LOGE("channel '%s' ~ Failed to receive dispatch signal. status=%d", + connection->getInputChannelName(), status); + return false; // remove the callback + } + + if (connection->messageInProgress) { + LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.", + connection->getInputChannelName()); + return true; + } + + status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent); + if (status) { + LOGW("channel '%s' ~ Failed to consume input event. status=%d", + connection->getInputChannelName(), status); + connection->inputConsumer.sendFinishedSignal(); + return true; + } + + connection->messageInProgress = true; + connection->messageSeqNum += 1; + + finishedToken = generateFinishedToken(receiveFd, connection->messageSeqNum); + + inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal); + } // release lock + + // Invoke the handler outside of the lock. + // + // Note: inputEvent is stored in a field of the connection object which could potentially + // become disposed due to the input channel being unregistered concurrently. + // For this reason, we explicitly keep the connection object alive by holding + // a strong pointer to it within this scope. We also grabbed a local reference to + // the input handler object itself for the same reason. + + int32_t inputEventType = inputEvent->getType(); + int32_t inputEventNature = inputEvent->getNature(); + + jobject inputEventObj; + jmethodID dispatchMethodId; + switch (inputEventType) { + case INPUT_EVENT_TYPE_KEY: +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Received key event.", connection->getInputChannelName()); +#endif + inputEventObj = android_view_KeyEvent_fromNative(env, + static_cast<KeyEvent*>(inputEvent)); + dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent; + break; + + case INPUT_EVENT_TYPE_MOTION: +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName()); +#endif + inputEventObj = android_view_MotionEvent_fromNative(env, + static_cast<MotionEvent*>(inputEvent)); + dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent; + break; + + default: + assert(false); // InputConsumer should prevent this from ever happening + inputEventObj = NULL; + } + + if (! inputEventObj) { + LOGW("channel '%s' ~ Failed to obtain DVM event object.", + connection->getInputChannelName()); + env->DeleteLocalRef(inputHandlerObjLocal); + q->finished(env, finishedToken, false); + return true; + } + +#if DEBUG_DISPATCH_CYCLE + LOGD("Invoking input handler."); +#endif + env->CallStaticVoidMethod(gInputQueueClassInfo.clazz, + dispatchMethodId, inputHandlerObjLocal, inputEventObj, + jint(inputEventNature), jlong(finishedToken)); +#if DEBUG_DISPATCH_CYCLE + LOGD("Returned from input handler."); +#endif + + if (env->ExceptionCheck()) { + LOGE("An exception occurred while invoking the input handler for an event."); + LOGE_EX(env); + env->ExceptionClear(); + + q->finished(env, finishedToken, true /*ignoreSpuriousFinish*/); + } + + env->DeleteLocalRef(inputEventObj); + env->DeleteLocalRef(inputHandlerObjLocal); + return true; +} + +jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, int32_t messageSeqNum) { + return (jlong(receiveFd) << 32) | jlong(messageSeqNum); +} + +void NativeInputQueue::parseFinishedToken(jlong finishedToken, + int32_t* outReceiveFd, uint32_t* outMessageIndex) { + *outReceiveFd = int32_t(finishedToken >> 32); + *outMessageIndex = uint32_t(finishedToken & 0xffffffff); +} + +// ---------------------------------------------------------------------------- + +NativeInputQueue::Connection::Connection(const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop) : + status(STATUS_NORMAL), inputChannel(inputChannel), inputConsumer(inputChannel), + pollLoop(pollLoop), inputHandlerObjGlobal(NULL), + messageSeqNum(0), messageInProgress(false) { +} + +NativeInputQueue::Connection::~Connection() { +} + +// ---------------------------------------------------------------------------- + +static NativeInputQueue gNativeInputQueue; + +static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, + jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) { + status_t status = gNativeInputQueue.registerInputChannel( + env, inputChannelObj, inputHandlerObj, messageQueueObj); + if (status) { + jniThrowRuntimeException(env, "Failed to register input channel. " + "Check logs for details."); + } +} + +static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, + jobject inputChannelObj) { + status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj); + if (status) { + jniThrowRuntimeException(env, "Failed to unregister input channel. " + "Check logs for details."); + } +} + +static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz, + jlong finishedToken) { + status_t status = gNativeInputQueue.finished( + env, finishedToken, false /*ignoreSpuriousFinish*/); + if (status) { + jniThrowRuntimeException(env, "Failed to finish input event. " + "Check logs for details."); + } +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gInputQueueMethods[] = { + /* name, signature, funcPtr */ + { "nativeRegisterInputChannel", + "(Landroid/view/InputChannel;Landroid/view/InputHandler;Landroid/os/MessageQueue;)V", + (void*)android_view_InputQueue_nativeRegisterInputChannel }, + { "nativeUnregisterInputChannel", + "(Landroid/view/InputChannel;)V", + (void*)android_view_InputQueue_nativeUnregisterInputChannel }, + { "nativeFinished", "(J)V", + (void*)android_view_InputQueue_nativeFinished } +}; + +#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_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find static method " methodName); + +int register_android_view_InputQueue(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/view/InputQueue", + gInputQueueMethods, NELEM(gInputQueueMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gInputQueueClassInfo.clazz, "android/view/InputQueue"); + + GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz, + "dispatchKeyEvent", + "(Landroid/view/InputHandler;Landroid/view/KeyEvent;IJ)V"); + + GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz, + "dispatchMotionEvent", + "(Landroid/view/InputHandler;Landroid/view/MotionEvent;IJ)V"); + return 0; +} + +} // namespace android diff --git a/core/jni/android_view_InputTarget.cpp b/core/jni/android_view_InputTarget.cpp new file mode 100644 index 0000000..e2a1f23 --- /dev/null +++ b/core/jni/android_view_InputTarget.cpp @@ -0,0 +1,97 @@ +/* + * 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/InputDispatchPolicy.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/core/jni/android_view_InputTarget.h b/core/jni/android_view_InputTarget.h new file mode 100644 index 0000000..9230b1b --- /dev/null +++ b/core/jni/android_view_InputTarget.h @@ -0,0 +1,31 @@ +/* + * 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. + */ + +#ifndef _ANDROID_VIEW_INPUTTARGET_H +#define _ANDROID_VIEW_INPUTTARGET_H + +#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 diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp new file mode 100644 index 0000000..df3b952 --- /dev/null +++ b/core/jni/android_view_KeyEvent.cpp @@ -0,0 +1,124 @@ +/* + * 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 "KeyEvent-JNI" + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <utils/Log.h> +#include <ui/Input.h> +#include "android_view_KeyEvent.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jmethodID ctor; + + jfieldID mMetaState; + jfieldID mAction; + jfieldID mKeyCode; + jfieldID mScanCode; + jfieldID mRepeatCount; + jfieldID mDeviceId; + jfieldID mFlags; + jfieldID mDownTime; + jfieldID mEventTime; + jfieldID mCharacters; +} gKeyEventClassInfo; + +// ---------------------------------------------------------------------------- + +jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) { + return env->NewObject(gKeyEventClassInfo.clazz, gKeyEventClassInfo.ctor, + nanoseconds_to_milliseconds(event->getDownTime()), + nanoseconds_to_milliseconds(event->getEventTime()), + event->getAction(), + event->getKeyCode(), + event->getRepeatCount(), + event->getMetaState(), + event->getDeviceId(), + event->getScanCode(), + event->getFlags()); +} + +void android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj, int32_t nature, + KeyEvent* event) { + jint metaState = env->GetIntField(eventObj, gKeyEventClassInfo.mMetaState); + jint action = env->GetIntField(eventObj, gKeyEventClassInfo.mAction); + jint keyCode = env->GetIntField(eventObj, gKeyEventClassInfo.mKeyCode); + jint scanCode = env->GetIntField(eventObj, gKeyEventClassInfo.mScanCode); + jint repeatCount = env->GetIntField(eventObj, gKeyEventClassInfo.mRepeatCount); + jint deviceId = env->GetIntField(eventObj, gKeyEventClassInfo.mDeviceId); + jint flags = env->GetIntField(eventObj, gKeyEventClassInfo.mFlags); + jlong downTime = env->GetLongField(eventObj, gKeyEventClassInfo.mDownTime); + jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime); + + event->initialize(deviceId, nature, action, flags, keyCode, scanCode, metaState, repeatCount, + milliseconds_to_nanoseconds(downTime), + milliseconds_to_nanoseconds(eventTime)); +} + +// ---------------------------------------------------------------------------- + +#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, fieldDescriptor) \ + var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ + 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_view_KeyEvent(JNIEnv* env) { + FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent"); + + GET_METHOD_ID(gKeyEventClassInfo.ctor, gKeyEventClassInfo.clazz, + "<init>", "(JJIIIIIII)V"); + + GET_FIELD_ID(gKeyEventClassInfo.mMetaState, gKeyEventClassInfo.clazz, + "mMetaState", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mAction, gKeyEventClassInfo.clazz, + "mAction", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mKeyCode, gKeyEventClassInfo.clazz, + "mKeyCode", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mScanCode, gKeyEventClassInfo.clazz, + "mScanCode", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mRepeatCount, gKeyEventClassInfo.clazz, + "mRepeatCount", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mDeviceId, gKeyEventClassInfo.clazz, + "mDeviceId", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mFlags, gKeyEventClassInfo.clazz, + "mFlags", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mDownTime, gKeyEventClassInfo.clazz, + "mDownTime", "J"); + GET_FIELD_ID(gKeyEventClassInfo.mEventTime, gKeyEventClassInfo.clazz, + "mEventTime", "J"); + GET_FIELD_ID(gKeyEventClassInfo.mCharacters, gKeyEventClassInfo.clazz, + "mCharacters", "Ljava/lang/String;"); + + return 0; +} + +} // namespace android diff --git a/core/jni/android_view_KeyEvent.h b/core/jni/android_view_KeyEvent.h new file mode 100644 index 0000000..3c71b1a --- /dev/null +++ b/core/jni/android_view_KeyEvent.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#ifndef _ANDROID_VIEW_KEYEVENT_H +#define _ANDROID_VIEW_KEYEVENT_H + +#include "jni.h" + +namespace android { + +class KeyEvent; + +/* Obtains an instance of a DVM KeyEvent object as a copy of a native KeyEvent instance. */ +extern jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event); + +/* Copies the contents of a DVM KeyEvent object to a native KeyEvent instance. */ +extern void android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj, int32_t nature, + KeyEvent* event); + +} // namespace android + +#endif // _ANDROID_OS_KEYEVENT_H diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp new file mode 100644 index 0000000..629c8fe --- /dev/null +++ b/core/jni/android_view_MotionEvent.cpp @@ -0,0 +1,310 @@ +/* + * 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 "MotionEvent-JNI" + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <utils/Log.h> +#include <ui/Input.h> +#include "android_view_MotionEvent.h" + +// Number of float items per entry in a DVM sample data array +#define NUM_SAMPLE_DATA 4 + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jmethodID obtain; + jmethodID recycle; + + jfieldID mDownTime; + jfieldID mEventTimeNano; + jfieldID mAction; + jfieldID mRawX; + jfieldID mRawY; + jfieldID mXPrecision; + jfieldID mYPrecision; + jfieldID mDeviceId; + jfieldID mEdgeFlags; + jfieldID mMetaState; + jfieldID mNumPointers; + jfieldID mNumSamples; + jfieldID mPointerIdentifiers; + jfieldID mDataSamples; + jfieldID mTimeSamples; +} gMotionEventClassInfo; + +// ---------------------------------------------------------------------------- + +jobject android_view_MotionEvent_fromNative(JNIEnv* env, const MotionEvent* event) { + jint numPointers = jint(event->getPointerCount()); + jint numHistoricalSamples = jint(event->getHistorySize()); + jint numSamples = numHistoricalSamples + 1; + + jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz, + gMotionEventClassInfo.obtain, numPointers, numSamples); + if (env->ExceptionCheck()) { + LOGE("An exception occurred while obtaining a motion event."); + LOGE_EX(env); + env->ExceptionClear(); + return NULL; + } + + // MotionEvent.mEventTimeNano is the time of the oldest sample because + // MotionEvent.addBatch does not update it as successive samples are added. + jlong eventTimeNano = numHistoricalSamples != 0 + ? event->getHistoricalEventTime(0) + : event->getEventTime(); + + env->SetLongField(eventObj, gMotionEventClassInfo.mDownTime, + nanoseconds_to_milliseconds(event->getDownTime())); + env->SetLongField(eventObj, gMotionEventClassInfo.mEventTimeNano, + eventTimeNano); + env->SetIntField(eventObj, gMotionEventClassInfo.mAction, + event->getAction()); + env->SetFloatField(eventObj, gMotionEventClassInfo.mRawX, + event->getRawX()); + env->SetFloatField(eventObj, gMotionEventClassInfo.mRawY, + event->getRawY()); + env->SetFloatField(eventObj, gMotionEventClassInfo.mXPrecision, + event->getXPrecision()); + env->SetFloatField(eventObj, gMotionEventClassInfo.mYPrecision, + event->getYPrecision()); + env->SetIntField(eventObj, gMotionEventClassInfo.mDeviceId, + event->getDeviceId()); + env->SetIntField(eventObj, gMotionEventClassInfo.mEdgeFlags, + event->getEdgeFlags()); + env->SetIntField(eventObj, gMotionEventClassInfo.mMetaState, + event->getMetaState()); + env->SetIntField(eventObj, gMotionEventClassInfo.mNumPointers, + numPointers); + env->SetIntField(eventObj, gMotionEventClassInfo.mNumSamples, + numSamples); + + jintArray pointerIdentifierArray = jintArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mPointerIdentifiers)); + jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mDataSamples)); + jlongArray timeSampleArray = jlongArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mTimeSamples)); + + jint* pointerIdentifiers = (jint*)env->GetPrimitiveArrayCritical(pointerIdentifierArray, NULL); + jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL); + jlong* timeSamples = (jlong*)env->GetPrimitiveArrayCritical(timeSampleArray, NULL); + + for (jint i = 0; i < numPointers; i++) { + pointerIdentifiers[i] = event->getPointerId(i); + } + + // Most recent data is in first slot of the DVM array, followed by the oldest, + // and then all others are in order. + + jfloat* currentDataSample = dataSamples; + jlong* currentTimeSample = timeSamples; + + *(currentTimeSample++) = nanoseconds_to_milliseconds(event->getEventTime()); + for (jint j = 0; j < numPointers; j++) { + *(currentDataSample++) = event->getX(j); + *(currentDataSample++) = event->getY(j); + *(currentDataSample++) = event->getPressure(j); + *(currentDataSample++) = event->getSize(j); + } + + for (jint i = 0; i < numHistoricalSamples; i++) { + *(currentTimeSample++) = nanoseconds_to_milliseconds(event->getHistoricalEventTime(i)); + for (jint j = 0; j < numPointers; j++) { + *(currentDataSample++) = event->getHistoricalX(j, i); + *(currentDataSample++) = event->getHistoricalY(j, i); + *(currentDataSample++) = event->getHistoricalPressure(j, i); + *(currentDataSample++) = event->getHistoricalSize(j, i); + } + } + + env->ReleasePrimitiveArrayCritical(pointerIdentifierArray, pointerIdentifiers, 0); + env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, 0); + env->ReleasePrimitiveArrayCritical(timeSampleArray, timeSamples, 0); + + env->DeleteLocalRef(pointerIdentifierArray); + env->DeleteLocalRef(dataSampleArray); + env->DeleteLocalRef(timeSampleArray); + return eventObj; +} + +void android_view_MotionEvent_toNative(JNIEnv* env, jobject eventObj, int32_t nature, + MotionEvent* event) { + // MotionEvent.mEventTimeNano is the time of the oldest sample because + // MotionEvent.addBatch does not update it as successive samples are added. + jlong downTime = env->GetLongField(eventObj, gMotionEventClassInfo.mDownTime); + jlong eventTimeNano = env->GetLongField(eventObj, gMotionEventClassInfo.mEventTimeNano); + jint action = env->GetIntField(eventObj, gMotionEventClassInfo.mAction); + jfloat rawX = env->GetFloatField(eventObj, gMotionEventClassInfo.mRawX); + jfloat rawY = env->GetFloatField(eventObj, gMotionEventClassInfo.mRawY); + jfloat xPrecision = env->GetFloatField(eventObj, gMotionEventClassInfo.mXPrecision); + jfloat yPrecision = env->GetFloatField(eventObj, gMotionEventClassInfo.mYPrecision); + jint deviceId = env->GetIntField(eventObj, gMotionEventClassInfo.mDeviceId); + jint edgeFlags = env->GetIntField(eventObj, gMotionEventClassInfo.mEdgeFlags); + jint metaState = env->GetIntField(eventObj, gMotionEventClassInfo.mMetaState); + jint numPointers = env->GetIntField(eventObj, gMotionEventClassInfo.mNumPointers); + jint numSamples = env->GetIntField(eventObj, gMotionEventClassInfo.mNumSamples); + jintArray pointerIdentifierArray = jintArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mPointerIdentifiers)); + jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mDataSamples)); + jlongArray timeSampleArray = jlongArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mTimeSamples)); + + LOG_FATAL_IF(numPointers == 0, "numPointers was zero"); + LOG_FATAL_IF(numSamples == 0, "numSamples was zero"); + + jint* pointerIdentifiers = (jint*)env->GetPrimitiveArrayCritical(pointerIdentifierArray, NULL); + jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL); + jlong* timeSamples = (jlong*)env->GetPrimitiveArrayCritical(timeSampleArray, NULL); + + // Most recent data is in first slot of the DVM array, followed by the oldest, + // and then all others are in order. eventTimeNano is the time of the oldest sample + // since MotionEvent.addBatch does not update it. + + jint numHistoricalSamples = numSamples - 1; + jint dataSampleStride = numPointers * NUM_SAMPLE_DATA; + + const jfloat* currentDataSample; + const jlong* currentTimeSample; + if (numHistoricalSamples == 0) { + currentDataSample = dataSamples; + currentTimeSample = timeSamples; + } else { + currentDataSample = dataSamples + dataSampleStride; + currentTimeSample = timeSamples + 1; + } + + PointerCoords pointerCoords[MAX_POINTERS]; + for (jint j = 0; j < numPointers; j++) { + pointerCoords[j].x = *(currentDataSample++); + pointerCoords[j].y = *(currentDataSample++); + pointerCoords[j].pressure = *(currentDataSample++); + pointerCoords[j].size = *(currentDataSample++); + } + + event->initialize(deviceId, nature, action, edgeFlags, metaState, + rawX, rawY, xPrecision, yPrecision, + milliseconds_to_nanoseconds(downTime), eventTimeNano, + numPointers, pointerIdentifiers, pointerCoords); + + while (numHistoricalSamples > 0) { + numHistoricalSamples -= 1; + if (numHistoricalSamples == 0) { + currentDataSample = dataSamples; + currentTimeSample = timeSamples; + } + + nsecs_t sampleEventTime = milliseconds_to_nanoseconds(*(currentTimeSample++)); + + for (jint j = 0; j < numPointers; j++) { + pointerCoords[j].x = *(currentDataSample++); + pointerCoords[j].y = *(currentDataSample++); + pointerCoords[j].pressure = *(currentDataSample++); + pointerCoords[j].size = *(currentDataSample++); + } + + event->addSample(sampleEventTime, pointerCoords); + } + + env->ReleasePrimitiveArrayCritical(pointerIdentifierArray, pointerIdentifiers, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(timeSampleArray, timeSamples, JNI_ABORT); + + env->DeleteLocalRef(pointerIdentifierArray); + env->DeleteLocalRef(dataSampleArray); + env->DeleteLocalRef(timeSampleArray); +} + +void android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj) { + env->CallVoidMethod(eventObj, gMotionEventClassInfo.recycle); + if (env->ExceptionCheck()) { + LOGW("An exception occurred while recycling a motion event."); + LOGW_EX(env); + env->ExceptionClear(); + } +} + +// ---------------------------------------------------------------------------- + +#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_STATIC_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ + var = env->GetStaticMethodID(clazz, methodName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find static method" methodName); + +#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ + var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ + 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_view_MotionEvent(JNIEnv* env) { + FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent"); + + GET_STATIC_METHOD_ID(gMotionEventClassInfo.obtain, gMotionEventClassInfo.clazz, + "obtain", "(II)Landroid/view/MotionEvent;"); + GET_METHOD_ID(gMotionEventClassInfo.recycle, gMotionEventClassInfo.clazz, + "recycle", "()V"); + + GET_FIELD_ID(gMotionEventClassInfo.mDownTime, gMotionEventClassInfo.clazz, + "mDownTime", "J"); + GET_FIELD_ID(gMotionEventClassInfo.mEventTimeNano, gMotionEventClassInfo.clazz, + "mEventTimeNano", "J"); + GET_FIELD_ID(gMotionEventClassInfo.mAction, gMotionEventClassInfo.clazz, + "mAction", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mRawX, gMotionEventClassInfo.clazz, + "mRawX", "F"); + GET_FIELD_ID(gMotionEventClassInfo.mRawY, gMotionEventClassInfo.clazz, + "mRawY", "F"); + GET_FIELD_ID(gMotionEventClassInfo.mXPrecision, gMotionEventClassInfo.clazz, + "mXPrecision", "F"); + GET_FIELD_ID(gMotionEventClassInfo.mYPrecision, gMotionEventClassInfo.clazz, + "mYPrecision", "F"); + GET_FIELD_ID(gMotionEventClassInfo.mDeviceId, gMotionEventClassInfo.clazz, + "mDeviceId", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mEdgeFlags, gMotionEventClassInfo.clazz, + "mEdgeFlags", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mMetaState, gMotionEventClassInfo.clazz, + "mMetaState", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mNumPointers, gMotionEventClassInfo.clazz, + "mNumPointers", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mNumSamples, gMotionEventClassInfo.clazz, + "mNumSamples", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mPointerIdentifiers, gMotionEventClassInfo.clazz, + "mPointerIdentifiers", "[I"); + GET_FIELD_ID(gMotionEventClassInfo.mDataSamples, gMotionEventClassInfo.clazz, + "mDataSamples", "[F"); + GET_FIELD_ID(gMotionEventClassInfo.mTimeSamples, gMotionEventClassInfo.clazz, + "mTimeSamples", "[J"); + + return 0; +} + +} // namespace android diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h new file mode 100644 index 0000000..03ee32f --- /dev/null +++ b/core/jni/android_view_MotionEvent.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#ifndef _ANDROID_VIEW_MOTIONEVENT_H +#define _ANDROID_VIEW_MOTIONEVENT_H + +#include "jni.h" + +namespace android { + +class MotionEvent; + +/* Obtains an instance of a DVM MotionEvent object as a copy of a native MotionEvent instance. */ +extern jobject android_view_MotionEvent_fromNative(JNIEnv* env, const MotionEvent* event); + +/* Copies the contents of a DVM MotionEvent object to a native MotionEvent instance. */ +extern void android_view_MotionEvent_toNative(JNIEnv* env, jobject eventObj, int32_t nature, + MotionEvent* event); + +/* Recycles a DVM MotionEvent object. */ +extern void android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj); + +} // namespace android + +#endif // _ANDROID_OS_KEYEVENT_H diff --git a/core/jni/android_view_ViewRoot.cpp b/core/jni/android_view_ViewRoot.cpp index 70ad8c5..5173bb8 100644 --- a/core/jni/android_view_ViewRoot.cpp +++ b/core/jni/android_view_ViewRoot.cpp @@ -80,38 +80,6 @@ static void android_view_ViewRoot_abandonGlCaches(JNIEnv* env, jobject) { SkGLCanvas::AbandonAllTextures(); } -static jintArray android_view_ViewRoot_makeInputChannel(JNIEnv* env, jobject) { - int fd[2]; - jint* arrayData = NULL; - - // Create the pipe - int err = socketpair(AF_LOCAL, SOCK_STREAM, 0, fd); - if (err != 0) { - fprintf(stderr, "socketpair() failed: %d\n", errno); - doThrow(env, "java/lang/RuntimeException", "Unable to create pipe"); - return NULL; - } - - // Set up the return array - jintArray array = env->NewIntArray(2); - if (env->ExceptionCheck()) { - fprintf(stderr, "Exception allocating fd array"); - goto bail; - } - - arrayData = env->GetIntArrayElements(array, 0); - arrayData[0] = fd[0]; - arrayData[1] = fd[1]; - env->ReleaseIntArrayElements(array, arrayData, 0); - - return array; - -bail: - env->DeleteLocalRef(array); - close(fd[0]); - close(fd[1]); - return NULL; -} // ---------------------------------------------------------------------------- @@ -121,9 +89,7 @@ static JNINativeMethod gMethods[] = { { "nativeShowFPS", "(Landroid/graphics/Canvas;I)V", (void*)android_view_ViewRoot_showFPS }, { "nativeAbandonGlCaches", "()V", - (void*)android_view_ViewRoot_abandonGlCaches }, - { "makeInputChannel", "()[I", - (void*)android_view_ViewRoot_makeInputChannel } + (void*)android_view_ViewRoot_abandonGlCaches } }; int register_android_view_ViewRoot(JNIEnv* env) { diff --git a/include/private/README b/include/private/README new file mode 100644 index 0000000..ee41492 --- /dev/null +++ b/include/private/README @@ -0,0 +1,4 @@ +This folder contains private include files. + +These include files are part of the private implementation details of +various framework components. Use at your peril. diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h index 3b18c77..d322a34 100644 --- a/include/ui/EventHub.h +++ b/include/ui/EventHub.h @@ -18,6 +18,7 @@ #ifndef _RUNTIME_EVENT_HUB_H #define _RUNTIME_EVENT_HUB_H +#include <android/input.h> #include <utils/String8.h> #include <utils/threads.h> #include <utils/Log.h> @@ -27,6 +28,31 @@ #include <linux/input.h> +/* These constants are not defined in linux/input.h but they are part of the multitouch + * input protocol. */ + +#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ +#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */ +#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */ +#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */ +#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */ +#define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */ +#define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */ +#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device (finger, pen, ...) */ +#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */ +#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */ +#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */ + +#define MT_TOOL_FINGER 0 /* Identifies a finger */ +#define MT_TOOL_PEN 1 /* Identifies a pen */ + +#define SYN_MT_REPORT 2 + +/* Convenience constants. */ + +#define BTN_FIRST 0x100 // first button scancode +#define BTN_LAST 0x15f // last button scancode + struct pollfd; namespace android { @@ -34,62 +60,101 @@ namespace android { class KeyLayoutMap; /* - * Grand Central Station for events. With a single call to waitEvent() - * you can wait for: - * - input events from the keypad of a real device - * - input events and meta-events (e.g. "quit") from the simulator - * - synthetic events from the runtime (e.g. "URL fetch completed") - * - real or forged "vsync" events + * Grand Central Station for events. * - * Do not instantiate this class. Instead, call startUp(). + * The event hub aggregates input events received across all known input + * devices on the system, including devices that may be emulated by the simulator + * environment. In addition, the event hub generates fake input events to indicate + * when devices are added or removed. + * + * The event hub provies a stream of input events (via the getEvent function). + * It also supports querying the current actual state of input devices such as identifying + * which keys are currently down. Finally, the event hub keeps track of the capabilities of + * individual input devices, such as their class and the set of key codes that they support. */ -class EventHub : public RefBase +class EventHubInterface : public virtual RefBase { +protected: + EventHubInterface() { } + virtual ~EventHubInterface() { } + +public: + // Synthetic raw event type codes produced when devices are added or removed. + enum { + DEVICE_ADDED = 0x10000000, + DEVICE_REMOVED = 0x20000000 + }; + + virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0; + + virtual String8 getDeviceName(int32_t deviceId) const = 0; + + virtual int getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue, + int* outMaxValue, int* outFlat, int* outFuzz) const = 0; + + virtual status_t scancodeToKeycode(int32_t deviceId, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const = 0; + + // exclude a particular device from opening + // this can be used to ignore input devices for sensors + virtual void addExcludedDevice(const char* deviceName) = 0; + + /* + * Wait for the next event to become available and return it. + * After returning, the EventHub holds onto a wake lock until the next call to getEvent. + * This ensures that the device will not go to sleep while the event is being processed. + * If the device needs to remain awake longer than that, then the caller is responsible + * for taking care of it (say, by poking the power manager user activity timer). + */ + virtual bool getEvent(int32_t* outDeviceId, int32_t* outType, + int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, + int32_t* outValue, nsecs_t* outWhen) = 0; + + /* + * Query current input state. + * deviceId may be -1 to search for the device automatically, filtered by class. + * deviceClasses may be -1 to ignore device class while searching. + */ + virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t scanCode) const = 0; + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t keyCode) const = 0; + virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses, + int32_t sw) const = 0; + + /* + * Examine key input devices for specific framework keycode support + */ + virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, + uint8_t* outFlags) const = 0; +}; + +class EventHub : public EventHubInterface { public: EventHub(); - + status_t errorCheck() const; + + virtual uint32_t getDeviceClasses(int32_t deviceId) const; - // bit fields for classes of devices. - enum { - CLASS_KEYBOARD = 0x00000001, - CLASS_ALPHAKEY = 0x00000002, - CLASS_TOUCHSCREEN = 0x00000004, - CLASS_TRACKBALL = 0x00000008, - CLASS_TOUCHSCREEN_MT= 0x00000010, - CLASS_DPAD = 0x00000020 - }; - uint32_t getDeviceClasses(int32_t deviceId) const; - - String8 getDeviceName(int32_t deviceId) const; + virtual String8 getDeviceName(int32_t deviceId) const; - int getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue, + virtual int getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue, int* outMaxValue, int* outFlat, int* outFuzz) const; - int getSwitchState(int sw) const; - int getSwitchState(int32_t deviceId, int sw) const; - - int getScancodeState(int key) const; - int getScancodeState(int32_t deviceId, int key) const; - - int getKeycodeState(int key) const; - int getKeycodeState(int32_t deviceId, int key) const; - - status_t scancodeToKeycode(int32_t deviceId, int scancode, + virtual status_t scancodeToKeycode(int32_t deviceId, int scancode, int32_t* outKeycode, uint32_t* outFlags) const; - // exclude a particular device from opening - // this can be used to ignore input devices for sensors - void addExcludedDevice(const char* deviceName); + virtual void addExcludedDevice(const char* deviceName); - // special type codes when devices are added/removed. - enum { - DEVICE_ADDED = 0x10000000, - DEVICE_REMOVED = 0x20000000 - }; - - // examine key input devices for specific framework keycode support - bool hasKeys(size_t numCodes, int32_t* keyCodes, uint8_t* outFlags); + virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t scanCode) const; + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t keyCode) const; + virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses, + int32_t sw) const; + + virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const; virtual bool getEvent(int32_t* outDeviceId, int32_t* outType, int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags, @@ -126,6 +191,10 @@ private: device_t* getDevice(int32_t deviceId) const; bool hasKeycode(device_t* device, int keycode) const; + int32_t getScanCodeStateLocked(device_t* device, int32_t scanCode) const; + int32_t getKeyCodeStateLocked(device_t* device, int32_t keyCode) const; + int32_t getSwitchStateLocked(device_t* device, int32_t sw) const; + // Protect all internal state. mutable Mutex mLock; @@ -151,7 +220,7 @@ private: // device ids that report particular switches. #ifdef EV_SW - int32_t mSwitches[SW_MAX+1]; + int32_t mSwitches[SW_MAX + 1]; #endif }; diff --git a/include/ui/Input.h b/include/ui/Input.h new file mode 100644 index 0000000..6a5c8a8 --- /dev/null +++ b/include/ui/Input.h @@ -0,0 +1,310 @@ +/* + * 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. + */ + +#ifndef _UI_INPUT_H +#define _UI_INPUT_H + +/** + * Native input event structures. + */ + +#include <android/input.h> +#include <utils/Vector.h> +#include <utils/Timers.h> + +/* + * Additional private constants not defined in ndk/ui/input.h. + */ +enum { + /* + * Private control to determine when an app is tracking a key sequence. + */ + KEY_EVENT_FLAG_START_TRACKING = 0x40000000 +}; + +/* + * Maximum number of pointers supported per motion event. + */ +#define MAX_POINTERS 10 + +namespace android { + +/* + * A raw event as retrieved from the EventHub. + */ +struct RawEvent { + nsecs_t when; + int32_t deviceId; + int32_t type; + int32_t scanCode; + int32_t keyCode; + int32_t value; + uint32_t flags; +}; + +/* + * Flags that flow alongside events in the input dispatch system to help with certain + * policy decisions such as waking from device sleep. + * + * TODO This enumeration should probably be split up or relabeled for clarity. + */ +enum { + /* These flags originate in RawEvents and are generally set in the key map. */ + + POLICY_FLAG_WAKE = 0x00000001, + POLICY_FLAG_WAKE_DROPPED = 0x00000002, + POLICY_FLAG_SHIFT = 0x00000004, + POLICY_FLAG_CAPS_LOCK = 0x00000008, + POLICY_FLAG_ALT = 0x00000010, + POLICY_FLAG_ALT_GR = 0x00000020, + POLICY_FLAG_MENU = 0x00000040, + POLICY_FLAG_LAUNCHER = 0x00000080, + + /* These flags are set by the input dispatch policy as it intercepts each event. */ + + // Indicates that the screen was off when the event was received and the event + // should wake the device. + POLICY_FLAG_WOKE_HERE = 0x10000000, + + // Indicates that the screen was dim when the event was received and the event + // should brighten the device. + POLICY_FLAG_BRIGHT_HERE = 0x20000000, +}; + +/* + * Pointer coordinate data. + */ +struct PointerCoords { + float x; + float y; + float pressure; + float size; +}; + +/* + * Input events. + */ +struct input_event_t { }; + +class InputEvent : public input_event_t { +public: + virtual ~InputEvent() { } + + virtual int32_t getType() const = 0; + + inline int32_t getDeviceId() const { return mDeviceId; } + + inline int32_t getNature() const { return mNature; } + +protected: + void initialize(int32_t deviceId, int32_t nature); + +private: + int32_t mDeviceId; + int32_t mNature; +}; + +class KeyEvent : public InputEvent { +public: + virtual ~KeyEvent() { } + + virtual int32_t getType() const { return INPUT_EVENT_TYPE_KEY; } + + inline int32_t getAction() const { return mAction; } + + inline int32_t getFlags() const { return mFlags; } + + inline int32_t getKeyCode() const { return mKeyCode; } + + inline int32_t getScanCode() const { return mScanCode; } + + inline int32_t getMetaState() const { return mMetaState; } + + inline int32_t getRepeatCount() const { return mRepeatCount; } + + inline nsecs_t getDownTime() const { return mDownTime; } + + inline nsecs_t getEventTime() const { return mEventTime; } + + void initialize( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t flags, + int32_t keyCode, + int32_t scanCode, + int32_t metaState, + int32_t repeatCount, + nsecs_t downTime, + nsecs_t eventTime); + +private: + int32_t mAction; + int32_t mFlags; + int32_t mKeyCode; + int32_t mScanCode; + int32_t mMetaState; + int32_t mRepeatCount; + nsecs_t mDownTime; + nsecs_t mEventTime; +}; + +class MotionEvent : public InputEvent { +public: + virtual ~MotionEvent() { } + + virtual int32_t getType() const { return INPUT_EVENT_TYPE_MOTION; } + + inline int32_t getAction() const { return mAction; } + + inline int32_t getEdgeFlags() const { return mEdgeFlags; } + + inline int32_t getMetaState() const { return mMetaState; } + + inline float getXPrecision() const { return mXPrecision; } + + inline float getYPrecision() const { return mYPrecision; } + + inline nsecs_t getDownTime() const { return mDownTime; } + + inline size_t getPointerCount() const { return mPointerIds.size(); } + + inline int32_t getPointerId(size_t pointerIndex) const { return mPointerIds[pointerIndex]; } + + inline nsecs_t getEventTime() const { return mSampleEventTimes[getHistorySize()]; } + + inline float getRawX() const { return mRawX; } + + inline float getRawY() const { return mRawY; } + + inline float getX(size_t pointerIndex) const { + return getCurrentPointerCoords(pointerIndex).x; + } + + inline float getY(size_t pointerIndex) const { + return getCurrentPointerCoords(pointerIndex).y; + } + + inline float getPressure(size_t pointerIndex) const { + return getCurrentPointerCoords(pointerIndex).pressure; + } + + inline float getSize(size_t pointerIndex) const { + return getCurrentPointerCoords(pointerIndex).size; + } + + inline size_t getHistorySize() const { return mSampleEventTimes.size() - 1; } + + inline nsecs_t getHistoricalEventTime(size_t historicalIndex) const { + return mSampleEventTimes[historicalIndex]; + } + + inline float getHistoricalX(size_t pointerIndex, size_t historicalIndex) const { + return getHistoricalPointerCoords(pointerIndex, historicalIndex).x; + } + + inline float getHistoricalY(size_t pointerIndex, size_t historicalIndex) const { + return getHistoricalPointerCoords(pointerIndex, historicalIndex).y; + } + + inline float getHistoricalPressure(size_t pointerIndex, size_t historicalIndex) const { + return getHistoricalPointerCoords(pointerIndex, historicalIndex).pressure; + } + + inline float getHistoricalSize(size_t pointerIndex, size_t historicalIndex) const { + return getHistoricalPointerCoords(pointerIndex, historicalIndex).size; + } + + void initialize( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t edgeFlags, + int32_t metaState, + float rawX, + float rawY, + float xPrecision, + float yPrecision, + nsecs_t downTime, + nsecs_t eventTime, + size_t pointerCount, + const int32_t* pointerIds, + const PointerCoords* pointerCoords); + + void addSample( + nsecs_t eventTime, + const PointerCoords* pointerCoords); + + void offsetLocation(float xOffset, float yOffset); + +private: + int32_t mAction; + int32_t mEdgeFlags; + int32_t mMetaState; + float mRawX; + float mRawY; + float mXPrecision; + float mYPrecision; + nsecs_t mDownTime; + Vector<int32_t> mPointerIds; + Vector<nsecs_t> mSampleEventTimes; + Vector<PointerCoords> mSamplePointerCoords; + + inline const PointerCoords& getCurrentPointerCoords(size_t pointerIndex) const { + return mSamplePointerCoords[getHistorySize() * getPointerCount() + pointerIndex]; + } + + inline const PointerCoords& getHistoricalPointerCoords( + size_t pointerIndex, size_t historicalIndex) const { + return mSamplePointerCoords[historicalIndex * getPointerCount() + pointerIndex]; + } +}; + +/* + * Input event factory. + */ +class InputEventFactoryInterface { +protected: + virtual ~InputEventFactoryInterface() { } + +public: + InputEventFactoryInterface() { } + + virtual KeyEvent* createKeyEvent() = 0; + virtual MotionEvent* createMotionEvent() = 0; +}; + +/* + * A simple input event factory implementation that uses a single preallocated instance + * of each type of input event that are reused for each request. + */ +class PreallocatedInputEventFactory : public InputEventFactoryInterface { +public: + PreallocatedInputEventFactory() { } + virtual ~PreallocatedInputEventFactory() { } + + virtual KeyEvent* createKeyEvent() { return & mKeyEvent; } + virtual MotionEvent* createMotionEvent() { return & mMotionEvent; } + +private: + KeyEvent mKeyEvent; + MotionEvent mMotionEvent; +}; + + +} // namespace android + +#endif // _UI_INPUT_H diff --git a/include/ui/InputDispatchPolicy.h b/include/ui/InputDispatchPolicy.h new file mode 100644 index 0000000..3546813 --- /dev/null +++ b/include/ui/InputDispatchPolicy.h @@ -0,0 +1,198 @@ +/* + * 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. + */ + +#ifndef _UI_INPUT_DISPATCH_POLICY_H +#define _UI_INPUT_DISPATCH_POLICY_H + +/** + * Native input dispatch policy. + */ + +#include <ui/Input.h> +#include <utils/Errors.h> +#include <utils/Vector.h> +#include <utils/Timers.h> +#include <utils/RefBase.h> +#include <utils/String8.h> + +namespace android { + +class InputChannel; + +/* + * 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. + */ +struct InputTarget { + enum { + /* This flag indicates that subsequent event delivery should be held until the + * current event is delivered to this target or a timeout occurs. */ + 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. */ + 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. */ + FLAG_CANCEL = 0x04 + }; + + // The input channel to be targeted. + sp<InputChannel> inputChannel; + + // Flags for the input target. + int32_t flags; + + // The timeout for event delivery to this target in nanoseconds. Or -1 if none. + nsecs_t timeout; + + // The x and y offset to add to a MotionEvent as it is delivered. + // (ignored for KeyEvents) + float xOffset, yOffset; +}; + +/* + * Input dispatch policy interface. + * + * The input dispatch policy is used by the input dispatcher to interact with the + * Window Manager and other system components. This separation of concerns keeps + * the input dispatcher relatively free of special case logic such as is required + * to determine the target of iput events, when to wake the device, how to interact + * with key guard, and when to transition to the home screen. + * + * The actual implementation is partially supported by callbacks into the DVM + * via JNI. This class is also mocked in the input dispatcher unit tests since + * it is an ideal test seam. + */ +class InputDispatchPolicyInterface : public virtual RefBase { +protected: + InputDispatchPolicyInterface() { } + virtual ~InputDispatchPolicyInterface() { } + +public: + enum { + ROTATION_0 = 0, + ROTATION_90 = 1, + ROTATION_180 = 2, + ROTATION_270 = 3 + }; + + enum { + // The input dispatcher should do nothing and discard the input unless other + // flags are set. + ACTION_NONE = 0, + + // The input dispatcher should dispatch the input to the application. + ACTION_DISPATCH = 0x00000001, + + // The input dispatcher should perform special filtering in preparation for + // a pending app switch. + ACTION_APP_SWITCH_COMING = 0x00000002, + + // The input dispatcher should add POLICY_FLAG_WOKE_HERE to the policy flags it + // passes through the dispatch pipeline. + ACTION_WOKE_HERE = 0x00000004, + + // The input dispatcher should add POLICY_FLAG_BRIGHT_HERE to the policy flags it + // passes through the dispatch pipeline. + ACTION_BRIGHT_HERE = 0x00000008 + }; + + enum { + TOUCHSCREEN_UNDEFINED = 0, + TOUCHSCREEN_NOTOUCH = 1, + TOUCHSCREEN_STYLUS = 2, + TOUCHSCREEN_FINGER = 3 + }; + + enum { + KEYBOARD_UNDEFINED = 0, + KEYBOARD_NOKEYS = 1, + KEYBOARD_QWERTY = 2, + KEYBOARD_12KEY = 3 + }; + + enum { + NAVIGATION_UNDEFINED = 0, + NAVIGATION_NONAV = 1, + NAVIGATION_DPAD = 2, + NAVIGATION_TRACKBALL = 3, + NAVIGATION_WHEEL = 4 + }; + + struct VirtualKeyDefinition { + int32_t scanCode; + + // configured position data, specified in display coords + int32_t centerX; + int32_t centerY; + int32_t width; + int32_t height; + }; + + /* Gets information about the display with the specified id. + * Returns true if the display info is available, false otherwise. + */ + virtual bool getDisplayInfo(int32_t displayId, + int32_t* width, int32_t* height, int32_t* orientation) = 0; + + virtual void notifyConfigurationChanged(nsecs_t when, + int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig) = 0; + + virtual void notifyLidSwitchChanged(nsecs_t when, bool lidOpen) = 0; + + 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) = 0; + + virtual int32_t interceptKey(nsecs_t when, int32_t deviceId, + bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) = 0; + + virtual int32_t interceptTrackball(nsecs_t when, bool buttonChanged, bool buttonDown, + bool rolled) = 0; + + virtual int32_t interceptTouch(nsecs_t when) = 0; + + virtual bool allowKeyRepeat() = 0; + virtual nsecs_t getKeyRepeatTimeout() = 0; + + virtual void getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags, + Vector<InputTarget>& outTargets) = 0; + virtual void getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags, + Vector<InputTarget>& outTargets) = 0; + + /* Determine whether to turn on some hacks we have to improve the touch interaction with a + * certain device whose screen currently is not all that good. + */ + virtual bool filterTouchEvents() = 0; + + /* Determine whether to turn on some hacks to improve touch interaction with another device + * where touch coordinate data can get corrupted. + */ + virtual bool filterJumpyTouchEvents() = 0; + + virtual void getVirtualKeyDefinitions(const String8& deviceName, + Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) = 0; + virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) = 0; +}; + +} // namespace android + +#endif // _UI_INPUT_DISPATCH_POLICY_H diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h new file mode 100644 index 0000000..bde07f2 --- /dev/null +++ b/include/ui/InputDispatcher.h @@ -0,0 +1,409 @@ +/* + * 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. + */ + +#ifndef _UI_INPUT_DISPATCHER_H +#define _UI_INPUT_DISPATCHER_H + +#include <ui/Input.h> +#include <ui/InputDispatchPolicy.h> +#include <ui/InputTransport.h> +#include <utils/KeyedVector.h> +#include <utils/Vector.h> +#include <utils/threads.h> +#include <utils/Timers.h> +#include <utils/RefBase.h> +#include <utils/String8.h> +#include <utils/PollLoop.h> +#include <utils/Pool.h> + +#include <stddef.h> +#include <unistd.h> + + +namespace android { + +/* Notifies the system about input events generated by the input reader. + * The dispatcher is expected to be mostly asynchronous. */ +class InputDispatcherInterface : public virtual RefBase { +protected: + InputDispatcherInterface() { } + virtual ~InputDispatcherInterface() { } + +public: + /* Runs a single iteration of the dispatch loop. + * Nominally processes one queued event, a timeout, or a response from an input consumer. + * + * This method should only be called on the input dispatcher thread. + */ + virtual void dispatchOnce() = 0; + + /* Notifies the dispatcher about new events. + * The dispatcher will process most of these events asynchronously although some + * policy processing may occur synchronously. + * + * These methods should only be called on the input reader thread. + */ + virtual void notifyConfigurationChanged(nsecs_t eventTime, + int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig) = 0; + virtual void notifyLidSwitchChanged(nsecs_t eventTime, bool lidOpen) = 0; + virtual void notifyAppSwitchComing(nsecs_t eventTime) = 0; + virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t nature, + uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, + int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0; + virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t nature, + uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags, + uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, nsecs_t downTime) = 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). + */ + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel) = 0; + virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0; +}; + +/* Dispatches events. */ +class InputDispatcher : public InputDispatcherInterface { +protected: + virtual ~InputDispatcher(); + +public: + explicit InputDispatcher(const sp<InputDispatchPolicyInterface>& policy); + + virtual void dispatchOnce(); + + virtual void notifyConfigurationChanged(nsecs_t eventTime, + int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig); + virtual void notifyLidSwitchChanged(nsecs_t eventTime, bool lidOpen); + virtual void notifyAppSwitchComing(nsecs_t eventTime); + virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t nature, + uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, + int32_t scanCode, int32_t metaState, nsecs_t downTime); + virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t nature, + uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags, + uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, nsecs_t downTime); + + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel); + virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel); + +private: + template <typename T> + struct Link { + T* next; + T* prev; + }; + + struct EventEntry : Link<EventEntry> { + enum { + TYPE_SENTINEL, + TYPE_CONFIGURATION_CHANGED, + TYPE_KEY, + TYPE_MOTION + }; + + int32_t refCount; + int32_t type; + nsecs_t eventTime; + }; + + struct ConfigurationChangedEntry : EventEntry { + int32_t touchScreenConfig; + int32_t keyboardConfig; + int32_t navigationConfig; + }; + + struct KeyEntry : EventEntry { + int32_t deviceId; + int32_t nature; + uint32_t policyFlags; + int32_t action; + int32_t flags; + int32_t keyCode; + int32_t scanCode; + int32_t metaState; + int32_t repeatCount; + nsecs_t downTime; + }; + + struct MotionSample { + MotionSample* next; + + nsecs_t eventTime; + PointerCoords pointerCoords[MAX_POINTERS]; + }; + + struct MotionEntry : EventEntry { + int32_t deviceId; + int32_t nature; + uint32_t policyFlags; + int32_t action; + int32_t metaState; + int32_t edgeFlags; + float xPrecision; + float yPrecision; + nsecs_t downTime; + uint32_t pointerCount; + int32_t pointerIds[MAX_POINTERS]; + + // Linked list of motion samples associated with this motion event. + MotionSample firstSample; + MotionSample* lastSample; + }; + + struct DispatchEntry : Link<DispatchEntry> { + EventEntry* eventEntry; // the event to dispatch + int32_t targetFlags; + float xOffset; + float yOffset; + nsecs_t timeout; + + // True if dispatch has started. + bool inProgress; + + // For motion events: + // Pointer to the first motion sample to dispatch in this cycle. + // Usually NULL to indicate that the list of motion samples begins at + // MotionEntry::firstSample. Otherwise, some samples were dispatched in a previous + // cycle and this pointer indicates the location of the first remainining sample + // to dispatch during the current cycle. + MotionSample* headMotionSample; + // Pointer to a motion sample to dispatch in the next cycle if the dispatcher was + // unable to send all motion samples during this cycle. On the next cycle, + // headMotionSample will be initialized to tailMotionSample and tailMotionSample + // will be set to NULL. + MotionSample* tailMotionSample; + }; + + template <typename T> + struct Queue { + T head; + T tail; + + inline Queue() { + head.prev = NULL; + head.next = & tail; + tail.prev = & head; + tail.next = NULL; + } + + inline bool isEmpty() { + return head.next == & tail; + } + + inline void enqueueAtTail(T* entry) { + T* last = tail.prev; + last->next = entry; + entry->prev = last; + entry->next = & tail; + tail.prev = entry; + } + + inline void enqueueAtHead(T* entry) { + T* first = head.next; + head.next = entry; + entry->prev = & head; + entry->next = first; + first->prev = entry; + } + + inline void dequeue(T* entry) { + entry->prev->next = entry->next; + entry->next->prev = entry->prev; + } + + inline T* dequeueAtHead() { + T* first = head.next; + dequeue(first); + return first; + } + }; + + /* Allocates queue entries and performs reference counting as needed. */ + class Allocator { + public: + Allocator(); + + ConfigurationChangedEntry* obtainConfigurationChangedEntry(); + KeyEntry* obtainKeyEntry(); + MotionEntry* obtainMotionEntry(); + DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry); + + void releaseEventEntry(EventEntry* entry); + void releaseConfigurationChangedEntry(ConfigurationChangedEntry* entry); + void releaseKeyEntry(KeyEntry* entry); + void releaseMotionEntry(MotionEntry* entry); + void releaseDispatchEntry(DispatchEntry* entry); + + void appendMotionSample(MotionEntry* motionEntry, + nsecs_t eventTime, int32_t pointerCount, const PointerCoords* pointerCoords); + void freeMotionSample(MotionSample* sample); + void freeMotionSampleList(MotionSample* head); + + private: + Pool<ConfigurationChangedEntry> mConfigurationChangeEntryPool; + Pool<KeyEntry> mKeyEntryPool; + Pool<MotionEntry> mMotionEntryPool; + Pool<MotionSample> mMotionSamplePool; + Pool<DispatchEntry> mDispatchEntryPool; + }; + + /* Manages the dispatch state associated with a single input channel. */ + class Connection : public RefBase { + protected: + virtual ~Connection(); + + public: + enum Status { + // Everything is peachy. + STATUS_NORMAL, + // An unrecoverable communication error has occurred. + STATUS_BROKEN, + // The client is not responding. + STATUS_NOT_RESPONDING, + // The input channel has been unregistered. + STATUS_ZOMBIE + }; + + Status status; + sp<InputChannel> inputChannel; + InputPublisher inputPublisher; + Queue<DispatchEntry> outboundQueue; + nsecs_t nextTimeoutTime; // next timeout time (LONG_LONG_MAX if none) + + nsecs_t lastEventTime; // the time when the event was originally captured + nsecs_t lastDispatchTime; // the time when the last event was dispatched + nsecs_t lastANRTime; // the time when the last ANR was recorded + + explicit Connection(const sp<InputChannel>& inputChannel); + + inline const char* getInputChannelName() { return inputChannel->getName().string(); } + + // Finds a DispatchEntry in the outbound queue associated with the specified event. + // Returns NULL if not found. + DispatchEntry* findQueuedDispatchEntryForEvent(const EventEntry* eventEntry) const; + + // Determine whether this connection has a pending synchronous dispatch target. + // Since there can only ever be at most one such target at a time, if there is one, + // it must be at the tail because nothing else can be enqueued after it. + inline bool hasPendingSyncTarget() { + return ! outboundQueue.isEmpty() + && (outboundQueue.tail.prev->targetFlags & InputTarget::FLAG_SYNC); + } + + // Gets the time since the current event was originally obtained from the input driver. + inline double getEventLatencyMillis(nsecs_t currentTime) { + return (currentTime - lastEventTime) / 1000000.0; + } + + // Gets the time since the current event entered the outbound dispatch queue. + inline double getDispatchLatencyMillis(nsecs_t currentTime) { + return (currentTime - lastDispatchTime) / 1000000.0; + } + + // Gets the time since the current event ANR was declared, if applicable. + inline double getANRLatencyMillis(nsecs_t currentTime) { + return (currentTime - lastANRTime) / 1000000.0; + } + + status_t initialize(); + }; + + sp<InputDispatchPolicyInterface> mPolicy; + + Mutex mLock; + + Queue<EventEntry> mInboundQueue; + Allocator mAllocator; + + sp<PollLoop> mPollLoop; + + // All registered connections mapped by receive pipe file descriptor. + KeyedVector<int, sp<Connection> > mConnectionsByReceiveFd; + + // Active connections are connections that have a non-empty outbound queue. + Vector<Connection*> mActiveConnections; + + // Pool of key and motion event objects used only to ask the input dispatch policy + // for the targets of an event that is to be dispatched. + KeyEvent mReusableKeyEvent; + MotionEvent mReusableMotionEvent; + + // The input targets that were most recently identified for dispatch. + // If there is a synchronous event dispatch in progress, the current input targets will + // remain unchanged until the dispatch has completed or been aborted. + Vector<InputTarget> mCurrentInputTargets; + + // Key repeat tracking. + // XXX Move this up to the input reader instead. + struct KeyRepeatState { + KeyEntry* lastKeyEntry; // or null if no repeat + nsecs_t nextRepeatTime; + } mKeyRepeatState; + + void resetKeyRepeatLocked(); + + // Process events that have just been dequeued from the head of the input queue. + void processConfigurationChangedLocked(nsecs_t currentTime, ConfigurationChangedEntry* entry); + void processKeyLocked(nsecs_t currentTime, KeyEntry* entry); + void processKeyRepeatLocked(nsecs_t currentTime); + void processMotionLocked(nsecs_t currentTime, MotionEntry* entry); + + // Identify input targets for an event and dispatch to them. + void identifyInputTargetsAndDispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry); + void identifyInputTargetsAndDispatchMotionLocked(nsecs_t currentTime, MotionEntry* entry); + void dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, EventEntry* entry, + bool resumeWithAppendedMotionSample); + + // Manage the dispatch cycle for a single connection. + void prepareDispatchCycleLocked(nsecs_t currentTime, Connection* connection, + EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample); + void startDispatchCycleLocked(nsecs_t currentTime, Connection* connection); + void finishDispatchCycleLocked(nsecs_t currentTime, Connection* connection); + bool timeoutDispatchCycleLocked(nsecs_t currentTime, Connection* connection); + bool abortDispatchCycleLocked(nsecs_t currentTime, Connection* connection, + bool broken); + static bool handleReceiveCallback(int receiveFd, int events, void* data); + + // Add or remove a connection to the mActiveConnections vector. + void activateConnectionLocked(Connection* connection); + void deactivateConnectionLocked(Connection* connection); + + // Interesting events that we might like to log or tell the framework about. + void onDispatchCycleStartedLocked(nsecs_t currentTime, Connection* connection); + void onDispatchCycleFinishedLocked(nsecs_t currentTime, Connection* connection, + bool recoveredFromANR); + void onDispatchCycleANRLocked(nsecs_t currentTime, Connection* connection); + void onDispatchCycleBrokenLocked(nsecs_t currentTime, Connection* connection); +}; + +/* Enqueues and dispatches input events, endlessly. */ +class InputDispatcherThread : public Thread { +public: + explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher); + ~InputDispatcherThread(); + +private: + virtual bool threadLoop(); + + sp<InputDispatcherInterface> mDispatcher; +}; + +} // namespace android + +#endif // _UI_INPUT_DISPATCHER_PRIV_H diff --git a/include/ui/InputManager.h b/include/ui/InputManager.h new file mode 100644 index 0000000..eb27513 --- /dev/null +++ b/include/ui/InputManager.h @@ -0,0 +1,134 @@ +/* + * 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. + */ + +#ifndef _UI_INPUT_MANAGER_H +#define _UI_INPUT_MANAGER_H + +/** + * Native input manager. + */ + +#include <ui/EventHub.h> +#include <ui/Input.h> +#include <ui/InputDispatchPolicy.h> +#include <utils/Errors.h> +#include <utils/Vector.h> +#include <utils/Timers.h> +#include <utils/RefBase.h> +#include <utils/String8.h> + +namespace android { + +class InputReader; +class InputDispatcher; +class InputReaderThread; +class InputDispatcherThread; + +/* + * The input manager is the core of the system event processing. + * + * The input manager uses two threads. + * + * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events, + * applies policy, and posts messages to a queue managed by the DispatcherThread. + * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the + * queue and asynchronously dispatches them to applications. + * + * By design, the InputReaderThread class and InputDispatcherThread class do not share any + * internal state. Moreover, all communication is done one way from the InputReaderThread + * into the InputDispatcherThread and never the reverse. Both classes may interact with the + * InputDispatchPolicy, however. + * + * The InputManager class never makes any calls into Java itself. Instead, the + * InputDispatchPolicy is responsible for performing all external interactions with the + * system, including calling DVM services. + */ +class InputManagerInterface : public virtual RefBase { +protected: + InputManagerInterface() { } + virtual ~InputManagerInterface() { } + +public: + /* Starts the input manager threads. */ + virtual status_t start() = 0; + + /* Stops the input manager threads and waits for them to exit. */ + virtual status_t stop() = 0; + + /* Registers an input channel prior to using it as the target of an event. */ + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel) = 0; + + /* Unregisters an input channel. */ + virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0; + + /* + * Query current input state. + * deviceId may be -1 to search for the device automatically, filtered by class. + * deviceClasses may be -1 to ignore device class while searching. + */ + virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t scanCode) const = 0; + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t keyCode) const = 0; + virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses, + int32_t sw) const = 0; + + /* Determine whether physical keys exist for the given framework-domain key codes. */ + virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const = 0; +}; + +class InputManager : public InputManagerInterface { +protected: + virtual ~InputManager(); + +public: + /* + * Creates an input manager that reads events from the given + * event hub and applies the given input dispatch policy. + */ + InputManager(const sp<EventHubInterface>& eventHub, + const sp<InputDispatchPolicyInterface>& policy); + + virtual status_t start(); + virtual status_t stop(); + + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel); + virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel); + + virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t scanCode) const; + virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t keyCode) const; + virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses, + int32_t sw) const; + virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const; + +private: + sp<EventHubInterface> mEventHub; + sp<InputDispatchPolicyInterface> mPolicy; + + sp<InputDispatcher> mDispatcher; + sp<InputDispatcherThread> mDispatcherThread; + + sp<InputReader> mReader; + sp<InputReaderThread> mReaderThread; + + void configureExcludedDevices(); +}; + +} // namespace android + +#endif // _UI_INPUT_MANAGER_H diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h new file mode 100644 index 0000000..7e7a64c --- /dev/null +++ b/include/ui/InputReader.h @@ -0,0 +1,472 @@ +/* + * 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. + */ + +#ifndef _UI_INPUT_READER_H +#define _UI_INPUT_READER_H + +#include <ui/EventHub.h> +#include <ui/Input.h> +#include <ui/InputDispatchPolicy.h> +#include <ui/InputDispatcher.h> +#include <utils/KeyedVector.h> +#include <utils/threads.h> +#include <utils/Timers.h> +#include <utils/RefBase.h> +#include <utils/String8.h> +#include <utils/BitSet.h> + +#include <stddef.h> +#include <unistd.h> + +/* Maximum pointer id value supported. + * (This is limited by our use of BitSet32 to track pointer assignments.) */ +#define MAX_POINTER_ID 32 + +/** Amount that trackball needs to move in order to generate a key event. */ +#define TRACKBALL_MOVEMENT_THRESHOLD 6 + +/* Slop distance for jumpy pointer detection. + * The vertical range of the screen divided by this is our epsilon value. */ +#define JUMPY_EPSILON_DIVISOR 212 + +/* Number of jumpy points to drop for touchscreens that need it. */ +#define JUMPY_TRANSITION_DROPS 3 +#define JUMPY_DROP_LIMIT 3 + +/* Maximum squared distance for averaging. + * If moving farther than this, turn of averaging to avoid lag in response. */ +#define AVERAGING_DISTANCE_LIMIT (75 * 75) + +/* Maximum number of historical samples to average. */ +#define AVERAGING_HISTORY_SIZE 5 + + +namespace android { + +extern int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState); +extern int32_t rotateKeyCode(int32_t keyCode, int32_t orientation); + +/* + * An input device structure tracks the state of a single input device. + * + * This structure is only used by ReaderThread and is not intended to be shared with + * DispatcherThread (because that would require locking). This works out fine because + * DispatcherThread is only interested in cooked event data anyways and does not need + * any of the low-level data from InputDevice. + */ +struct InputDevice { + struct AbsoluteAxisInfo { + int32_t minValue; // minimum value + int32_t maxValue; // maximum value + int32_t range; // range of values, equal to maxValue - minValue + int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8 + int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise + }; + + struct VirtualKey { + int32_t keyCode; + int32_t scanCode; + uint32_t flags; + + // computed hit box, specified in touch screen coords based on known display size + int32_t hitLeft; + int32_t hitTop; + int32_t hitRight; + int32_t hitBottom; + + inline bool isHit(int32_t x, int32_t y) const { + return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom; + } + }; + + struct KeyboardState { + struct Current { + int32_t metaState; + nsecs_t downTime; // time of most recent key down + } current; + + void reset(); + }; + + struct TrackballState { + struct Accumulator { + enum { + FIELD_BTN_MOUSE = 1, + FIELD_REL_X = 2, + FIELD_REL_Y = 4 + }; + + uint32_t fields; + + bool btnMouse; + int32_t relX; + int32_t relY; + + inline void clear() { + fields = 0; + } + + inline bool isDirty() { + return fields != 0; + } + } accumulator; + + struct Current { + bool down; + nsecs_t downTime; + } current; + + struct Precalculated { + float xScale; + float yScale; + float xPrecision; + float yPrecision; + } precalculated; + + void reset(); + }; + + struct SingleTouchScreenState { + struct Accumulator { + enum { + FIELD_BTN_TOUCH = 1, + FIELD_ABS_X = 2, + FIELD_ABS_Y = 4, + FIELD_ABS_PRESSURE = 8, + FIELD_ABS_TOOL_WIDTH = 16 + }; + + uint32_t fields; + + bool btnTouch; + int32_t absX; + int32_t absY; + int32_t absPressure; + int32_t absToolWidth; + + inline void clear() { + fields = 0; + } + + inline bool isDirty() { + return fields != 0; + } + } accumulator; + + struct Current { + bool down; + int32_t x; + int32_t y; + int32_t pressure; + int32_t size; + } current; + + void reset(); + }; + + struct MultiTouchScreenState { + struct Accumulator { + enum { + FIELD_ABS_MT_POSITION_X = 1, + FIELD_ABS_MT_POSITION_Y = 2, + FIELD_ABS_MT_TOUCH_MAJOR = 4, + FIELD_ABS_MT_WIDTH_MAJOR = 8, + FIELD_ABS_MT_TRACKING_ID = 16 + }; + + uint32_t pointerCount; + struct Pointer { + uint32_t fields; + + int32_t absMTPositionX; + int32_t absMTPositionY; + int32_t absMTTouchMajor; + int32_t absMTWidthMajor; + int32_t absMTTrackingId; + + inline void clear() { + fields = 0; + } + } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks + + inline void clear() { + pointerCount = 0; + pointers[0].clear(); + } + + inline bool isDirty() { + return pointerCount != 0; + } + } accumulator; + + void reset(); + }; + + struct PointerData { + uint32_t id; + int32_t x; + int32_t y; + int32_t pressure; + int32_t size; + }; + + struct TouchData { + uint32_t pointerCount; + PointerData pointers[MAX_POINTERS]; + BitSet32 idBits; + uint32_t idToIndex[MAX_POINTER_ID]; + + void copyFrom(const TouchData& other); + + inline void clear() { + pointerCount = 0; + idBits.clear(); + } + }; + + // common state used for both single-touch and multi-touch screens after the initial + // touch decoding has been performed + struct TouchScreenState { + Vector<VirtualKey> virtualKeys; + + struct Parameters { + bool useBadTouchFilter; + bool useJumpyTouchFilter; + bool useAveragingTouchFilter; + + AbsoluteAxisInfo xAxis; + AbsoluteAxisInfo yAxis; + AbsoluteAxisInfo pressureAxis; + AbsoluteAxisInfo sizeAxis; + } parameters; + + // The touch data of the current sample being processed. + TouchData currentTouch; + + // The touch data of the previous sample that was processed. This is updated + // incrementally while the current sample is being processed. + TouchData lastTouch; + + // The time the primary pointer last went down. + nsecs_t downTime; + + struct CurrentVirtualKeyState { + bool down; + nsecs_t downTime; + int32_t keyCode; + int32_t scanCode; + } currentVirtualKey; + + struct AveragingTouchFilterState { + // Individual history tracks are stored by pointer id + uint32_t historyStart[MAX_POINTERS]; + uint32_t historyEnd[MAX_POINTERS]; + struct { + struct { + int32_t x; + int32_t y; + int32_t pressure; + } pointers[MAX_POINTERS]; + } historyData[AVERAGING_HISTORY_SIZE]; + } averagingTouchFilter; + + struct JumpTouchFilterState { + int32_t jumpyPointsDropped; + } jumpyTouchFilter; + + struct Precalculated { + float xScale; + float yScale; + float pressureScale; + float sizeScale; + } precalculated; + + void reset(); + + bool applyBadTouchFilter(); + bool applyJumpyTouchFilter(); + void applyAveragingTouchFilter(); + void calculatePointerIds(); + + bool isPointInsideDisplay(int32_t x, int32_t y) const; + }; + + InputDevice(int32_t id, uint32_t classes, String8 name); + + int32_t id; + uint32_t classes; + String8 name; + bool ignored; + + KeyboardState keyboard; + TrackballState trackball; + TouchScreenState touchScreen; + union { + SingleTouchScreenState singleTouchScreen; + MultiTouchScreenState multiTouchScreen; + }; + + void reset(); + + inline bool isKeyboard() const { return classes & INPUT_DEVICE_CLASS_KEYBOARD; } + inline bool isAlphaKey() const { return classes & INPUT_DEVICE_CLASS_ALPHAKEY; } + inline bool isTrackball() const { return classes & INPUT_DEVICE_CLASS_TRACKBALL; } + inline bool isDPad() const { return classes & INPUT_DEVICE_CLASS_DPAD; } + inline bool isSingleTouchScreen() const { return (classes + & (INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT)) + == INPUT_DEVICE_CLASS_TOUCHSCREEN; } + inline bool isMultiTouchScreen() const { return classes + & INPUT_DEVICE_CLASS_TOUCHSCREEN_MT; } + inline bool isTouchScreen() const { return classes + & (INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT); } +}; + + +/* Processes raw input events and sends cooked event data to an input dispatcher + * in accordance with the input dispatch policy. */ +class InputReaderInterface : public virtual RefBase { +protected: + InputReaderInterface() { } + virtual ~InputReaderInterface() { } + +public: + /* Runs a single iteration of the processing loop. + * Nominally reads and processes one incoming message from the EventHub. + * + * This method should be called on the input reader thread. + */ + virtual void loopOnce() = 0; + + /* Gets the current virtual key. Returns false if not down. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual bool getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const = 0; +}; + +/* The input reader reads raw event data from the event hub and processes it into input events + * that it sends to the input dispatcher. Some functions of the input reader are controlled + * by the input dispatch policy, such as early event filtering in low power states. + */ +class InputReader : public InputReaderInterface { +public: + InputReader(const sp<EventHubInterface>& eventHub, + const sp<InputDispatchPolicyInterface>& policy, + const sp<InputDispatcherInterface>& dispatcher); + virtual ~InputReader(); + + virtual void loopOnce(); + + virtual bool getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const; + +private: + // Lock that must be acquired while manipulating state that may be concurrently accessed + // from other threads by input state query methods. It should be held for as short a + // time as possible. + // + // Exported state: + // - global virtual key code and scan code + // - device list and immutable properties of devices such as id, name, and class + // (but not other internal device state) + mutable Mutex mExportedStateLock; + + // current virtual key information + int32_t mGlobalVirtualKeyCode; + int32_t mGlobalVirtualScanCode; + + // combined key meta state + int32_t mGlobalMetaState; + + sp<EventHubInterface> mEventHub; + sp<InputDispatchPolicyInterface> mPolicy; + sp<InputDispatcherInterface> mDispatcher; + + KeyedVector<int32_t, InputDevice*> mDevices; + + // display properties needed to translate touch screen coordinates into display coordinates + int32_t mDisplayOrientation; + int32_t mDisplayWidth; + int32_t mDisplayHeight; + + // low-level input event decoding + void process(const RawEvent* rawEvent); + void handleDeviceAdded(const RawEvent* rawEvent); + void handleDeviceRemoved(const RawEvent* rawEvent); + void handleSync(const RawEvent* rawEvent); + void handleKey(const RawEvent* rawEvent); + void handleRelativeMotion(const RawEvent* rawEvent); + void handleAbsoluteMotion(const RawEvent* rawEvent); + void handleSwitch(const RawEvent* rawEvent); + + // input policy processing and dispatch + void onKey(nsecs_t when, InputDevice* device, bool down, + int32_t keyCode, int32_t scanCode, uint32_t policyFlags); + void onSwitch(nsecs_t when, InputDevice* device, bool down, int32_t code); + void onSingleTouchScreenStateChanged(nsecs_t when, InputDevice* device); + void onMultiTouchScreenStateChanged(nsecs_t when, InputDevice* device); + void onTouchScreenChanged(nsecs_t when, InputDevice* device, bool havePointerIds); + void onTrackballStateChanged(nsecs_t when, InputDevice* device); + void onConfigurationChanged(nsecs_t when); + + bool applyStandardInputDispatchPolicyActions(nsecs_t when, + int32_t policyActions, uint32_t* policyFlags); + + bool consumeVirtualKeyTouches(nsecs_t when, InputDevice* device, uint32_t policyFlags); + void dispatchVirtualKey(nsecs_t when, InputDevice* device, uint32_t policyFlags, + int32_t keyEventAction, int32_t keyEventFlags); + void dispatchTouches(nsecs_t when, InputDevice* device, uint32_t policyFlags); + void dispatchTouch(nsecs_t when, InputDevice* device, uint32_t policyFlags, + InputDevice::TouchData* touch, BitSet32 idBits, int32_t motionEventAction); + + // display + void resetDisplayProperties(); + bool refreshDisplayProperties(); + + // device management + InputDevice* getDevice(int32_t deviceId); + InputDevice* getNonIgnoredDevice(int32_t deviceId); + void addDevice(nsecs_t when, int32_t deviceId); + void removeDevice(nsecs_t when, InputDevice* device); + void configureDevice(InputDevice* device); + void configureDeviceForCurrentDisplaySize(InputDevice* device); + void configureVirtualKeys(InputDevice* device); + void configureAbsoluteAxisInfo(InputDevice* device, int axis, const char* name, + InputDevice::AbsoluteAxisInfo* out); + + // global meta state management for all devices + void resetGlobalMetaState(); + int32_t globalMetaState(); + + // virtual key management + void updateGlobalVirtualKeyState(); +}; + + +/* Reads raw events from the event hub and processes them, endlessly. */ +class InputReaderThread : public Thread { +public: + InputReaderThread(const sp<InputReaderInterface>& reader); + virtual ~InputReaderThread(); + +private: + sp<InputReaderInterface> mReader; + + virtual bool threadLoop(); +}; + +} // namespace android + +#endif // _UI_INPUT_READER_H diff --git a/include/ui/InputTransport.h b/include/ui/InputTransport.h new file mode 100644 index 0000000..9537523 --- /dev/null +++ b/include/ui/InputTransport.h @@ -0,0 +1,331 @@ +/* + * 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. + */ + +#ifndef _UI_INPUT_TRANSPORT_H +#define _UI_INPUT_TRANSPORT_H + +/** + * Native input transport. + * + * Uses anonymous shared memory as a whiteboard for sending input events from an + * InputPublisher to an InputConsumer and ensuring appropriate synchronization. + * One interesting feature is that published events can be updated in place as long as they + * have not yet been consumed. + * + * The InputPublisher and InputConsumer only take care of transferring event data + * over an InputChannel and sending synchronization signals. The InputDispatcher and InputQueue + * build on these abstractions to add multiplexing and queueing. + */ + +#include <semaphore.h> +#include <ui/Input.h> +#include <utils/Errors.h> +#include <utils/Timers.h> +#include <utils/RefBase.h> +#include <utils/String8.h> + +namespace android { + +/* + * An input channel consists of a shared memory buffer and a pair of pipes + * used to send input messages from an InputPublisher to an InputConsumer + * across processes. Each channel has a descriptive name for debugging purposes. + * + * Each endpoint has its own InputChannel object that specifies its own file descriptors. + * + * The input channel is closed when all references to it are released. + */ +class InputChannel : public RefBase { +protected: + virtual ~InputChannel(); + +public: + InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd, + int32_t sendPipeFd); + + /* Creates a pair of input channels and their underlying shared memory buffers + * and pipes. + * + * Returns OK on success. + */ + static status_t openInputChannelPair(const String8& name, + InputChannel** outServerChannel, InputChannel** outClientChannel); + + inline String8 getName() const { return mName; } + inline int32_t getAshmemFd() const { return mAshmemFd; } + inline int32_t getReceivePipeFd() const { return mReceivePipeFd; } + inline int32_t getSendPipeFd() const { return mSendPipeFd; } + + /* Sends a signal to the other endpoint. + * + * Returns OK on success. + * Errors probably indicate that the channel is broken. + */ + status_t sendSignal(char signal); + + /* Receives a signal send by the other endpoint. + * (Should only call this after poll() indicates that the receivePipeFd has available input.) + * + * Returns OK on success. + * Returns WOULD_BLOCK if there is no signal present. + * Other errors probably indicate that the channel is broken. + */ + status_t receiveSignal(char* outSignal); + +private: + String8 mName; + int32_t mAshmemFd; + int32_t mReceivePipeFd; + int32_t mSendPipeFd; +}; + +/* + * Private intermediate representation of input events as messages written into an + * ashmem buffer. + */ +struct InputMessage { + /* Semaphore count is set to 1 when the message is published. + * It becomes 0 transiently while the publisher updates the message. + * It becomes 0 permanently when the consumer consumes the message. + */ + sem_t semaphore; + + /* Initialized to false by the publisher. + * Set to true by the consumer when it consumes the message. + */ + bool consumed; + + int32_t type; + + struct SampleData { + nsecs_t eventTime; + PointerCoords coords[0]; // variable length + }; + + int32_t deviceId; + int32_t nature; + + union { + struct { + int32_t action; + int32_t flags; + int32_t keyCode; + int32_t scanCode; + int32_t metaState; + int32_t repeatCount; + nsecs_t downTime; + nsecs_t eventTime; + } key; + + struct { + int32_t action; + int32_t metaState; + int32_t edgeFlags; + nsecs_t downTime; + float xOffset; + float yOffset; + float xPrecision; + float yPrecision; + size_t pointerCount; + int32_t pointerIds[MAX_POINTERS]; + size_t sampleCount; + SampleData sampleData[0]; // variable length + } motion; + }; + + /* Gets the number of bytes to add to step to the next SampleData object in a motion + * event message for a given number of pointers. + */ + static inline size_t sampleDataStride(size_t pointerCount) { + return sizeof(InputMessage::SampleData) + pointerCount * sizeof(PointerCoords); + } + + /* Adds the SampleData stride to the given pointer. */ + static inline SampleData* sampleDataPtrIncrement(SampleData* ptr, size_t stride) { + return reinterpret_cast<InputMessage::SampleData*>(reinterpret_cast<char*>(ptr) + stride); + } +}; + +/* + * Publishes input events to an anonymous shared memory buffer. + * Uses atomic operations to coordinate shared access with a single concurrent consumer. + */ +class InputPublisher { +public: + /* Creates a publisher associated with an input channel. */ + explicit InputPublisher(const sp<InputChannel>& channel); + + /* Destroys the publisher and releases its input channel. */ + ~InputPublisher(); + + /* Gets the underlying input channel. */ + inline sp<InputChannel> getChannel() { return mChannel; } + + /* Prepares the publisher for use. Must be called before it is used. + * Returns OK on success. + * + * This method implicitly calls reset(). */ + status_t initialize(); + + /* Resets the publisher to its initial state and unpins its ashmem buffer. + * Returns OK on success. + * + * Should be called after an event has been consumed to release resources used by the + * publisher until the next event is ready to be published. + */ + status_t reset(); + + /* Publishes a key event to the ashmem buffer. + * + * Returns OK on success. + * Returns INVALID_OPERATION if the publisher has not been reset. + */ + status_t publishKeyEvent( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t flags, + int32_t keyCode, + int32_t scanCode, + int32_t metaState, + int32_t repeatCount, + nsecs_t downTime, + nsecs_t eventTime); + + /* Publishes a motion event to the ashmem buffer. + * + * Returns OK on success. + * Returns INVALID_OPERATION if the publisher has not been reset. + * Returns BAD_VALUE if pointerCount is less than 1 or greater than MAX_POINTERS. + */ + status_t publishMotionEvent( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t edgeFlags, + int32_t metaState, + float xOffset, + float yOffset, + float xPrecision, + float yPrecision, + nsecs_t downTime, + nsecs_t eventTime, + size_t pointerCount, + const int32_t* pointerIds, + const PointerCoords* pointerCoords); + + /* Appends a motion sample to a motion event unless already consumed. + * + * Returns OK on success. + * Returns INVALID_OPERATION if the current event is not a MOTION_EVENT_ACTION_MOVE event. + * Returns FAILED_TRANSACTION if the current event has already been consumed. + * Returns NO_MEMORY if the buffer is full and no additional samples can be added. + */ + status_t appendMotionSample( + nsecs_t eventTime, + const PointerCoords* pointerCoords); + + /* Sends a dispatch signal to the consumer to inform it that a new message is available. + * + * Returns OK on success. + * Errors probably indicate that the channel is broken. + */ + status_t sendDispatchSignal(); + + /* Receives the finished signal from the consumer in reply to the original dispatch signal. + * + * Returns OK on success. + * Returns WOULD_BLOCK if there is no signal present. + * Other errors probably indicate that the channel is broken. + */ + status_t receiveFinishedSignal(); + +private: + sp<InputChannel> mChannel; + + size_t mAshmemSize; + InputMessage* mSharedMessage; + bool mPinned; + bool mSemaphoreInitialized; + bool mWasDispatched; + + size_t mMotionEventPointerCount; + InputMessage::SampleData* mMotionEventSampleDataTail; + size_t mMotionEventSampleDataStride; + + status_t publishInputEvent( + int32_t type, + int32_t deviceId, + int32_t nature); +}; + +/* + * Consumes input events from an anonymous shared memory buffer. + * Uses atomic operations to coordinate shared access with a single concurrent publisher. + */ +class InputConsumer { +public: + /* Creates a consumer associated with an input channel. */ + explicit InputConsumer(const sp<InputChannel>& channel); + + /* Destroys the consumer and releases its input channel. */ + ~InputConsumer(); + + /* Gets the underlying input channel. */ + inline sp<InputChannel> getChannel() { return mChannel; } + + /* Prepares the consumer for use. Must be called before it is used. */ + status_t initialize(); + + /* Consumes the input event in the buffer and copies its contents into + * an InputEvent object created using the specified factory. + * This operation will block if the publisher is updating the event. + * + * Returns OK on success. + * Returns INVALID_OPERATION if there is no currently published event. + * Returns NO_MEMORY if the event could not be created. + */ + status_t consume(InputEventFactoryInterface* factory, InputEvent** event); + + /* Sends a finished signal to the publisher to inform it that the current message is + * finished processing. + * + * Returns OK on success. + * Errors probably indicate that the channel is broken. + */ + status_t sendFinishedSignal(); + + /* Receives the dispatched signal from the publisher. + * + * Returns OK on success. + * Returns WOULD_BLOCK if there is no signal present. + * Other errors probably indicate that the channel is broken. + */ + status_t receiveDispatchSignal(); + +private: + sp<InputChannel> mChannel; + + size_t mAshmemSize; + InputMessage* mSharedMessage; + + void populateKeyEvent(KeyEvent* keyEvent) const; + void populateMotionEvent(MotionEvent* motionEvent) const; +}; + +} // namespace android + +#endif // _UI_INPUT_TRANSPORT_H diff --git a/include/utils/BitSet.h b/include/utils/BitSet.h new file mode 100644 index 0000000..19c8bf0 --- /dev/null +++ b/include/utils/BitSet.h @@ -0,0 +1,67 @@ +/* + * 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. + */ + +#ifndef UTILS_BITSET_H +#define UTILS_BITSET_H + +#include <stdint.h> + +/* + * Contains some bit manipulation helpers. + */ + +namespace android { + +// A simple set of 32 bits that can be individually marked or cleared. +struct BitSet32 { + uint32_t value; + + inline BitSet32() : value(0) { } + explicit inline BitSet32(uint32_t value) : value(value) { } + + // Gets the value associated with a particular bit index. + static inline uint32_t valueForBit(uint32_t n) { return 0x80000000 >> n; } + + // Clears the bit set. + inline void clear() { value = 0; } + + // Returns true if the bit set does not contain any marked bits. + inline bool isEmpty() const { return ! value; } + + // Returns true if the specified bit is marked. + inline bool hasBit(uint32_t n) const { return value & valueForBit(n); } + + // Marks the specified bit. + inline void markBit(uint32_t n) { value |= valueForBit(n); } + + // Clears the specified bit. + inline void clearBit(uint32_t n) { value &= ~ valueForBit(n); } + + // Finds the first marked bit in the set. + // Result is undefined if all bits are unmarked. + inline uint32_t firstMarkedBit() const { return __builtin_clz(value); } + + // Finds the first unmarked bit in the set. + // Result is undefined if all bits are marked. + inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); } + + inline bool operator== (const BitSet32& other) const { return value == other.value; } + inline bool operator!= (const BitSet32& other) const { return value != other.value; } +}; + +} // namespace android + +#endif // UTILS_BITSET_H diff --git a/include/utils/Buffer.h b/include/utils/Buffer.h deleted file mode 100644 index 8e22b0f..0000000 --- a/include/utils/Buffer.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2008 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. - */ - -#ifndef __UTILS_BUFFER_H__ -#define __UTILS_BUFFER_H__ 1 - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -namespace android { - -class Buffer -{ -private: - char *buf; - int bufsiz; - int used; - void ensureCapacity(int len); - - void - makeRoomFor(int len) - { - if (len + used >= bufsiz) { - bufsiz = (len + used) * 3/2 + 2; - char *blah = new char[bufsiz]; - - memcpy(blah, buf, used); - delete[] buf; - buf = blah; - } - } - -public: - Buffer() - { - bufsiz = 16; - buf = new char[bufsiz]; - clear(); - } - - ~Buffer() - { - delete[] buf; - } - - void - clear() - { - buf[0] = '\0'; - used = 0; - } - - int - length() - { - return used; - } - - void - append(const char c) - { - makeRoomFor(1); - buf[used] = c; - used++; - buf[used] = '\0'; - } - - void - append(const char *s, int len) - { - makeRoomFor(len); - - memcpy(buf + used, s, len); - used += len; - buf[used] = '\0'; - } - - void - append(const char *s) - { - append(s, strlen(s)); - } - - char * - getBytes() - { - return buf; - } -}; - -}; // namespace android - -#endif diff --git a/include/utils/PollLoop.h b/include/utils/PollLoop.h new file mode 100644 index 0000000..2ec39fe --- /dev/null +++ b/include/utils/PollLoop.h @@ -0,0 +1,137 @@ +/* + * 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. + */ + +#ifndef UTILS_POLL_LOOP_H +#define UTILS_POLL_LOOP_H + +#include <utils/Vector.h> +#include <utils/threads.h> + +#include <poll.h> + +namespace android { + +/** + * A basic file descriptor polling loop based on poll() with callbacks. + */ +class PollLoop : public RefBase { +protected: + virtual ~PollLoop(); + +public: + PollLoop(); + + /** + * A callback that it to be invoked when an event occurs on a file descriptor. + * Specifies the events that were triggered and the user data provided when the + * callback was set. + * + * Returns true if the callback should be kept, false if it should be removed automatically + * after the callback returns. + */ + typedef bool (*Callback)(int fd, int events, void* data); + + /** + * Performs a single call to poll() with optional timeout in milliseconds. + * Invokes callbacks for all file descriptors on which an event occurred. + * + * If the timeout is zero, returns immediately without blocking. + * If the timeout is negative, waits indefinitely until awoken. + * + * Returns true if a callback was invoked or if the loop was awoken by wake(). + * Returns false if a timeout or error occurred. + * + * This method must only be called on the main thread. + * This method blocks until either a file descriptor is signalled, a timeout occurs, + * or wake() is called. + * This method does not return until it has finished invoking the appropriate callbacks + * for all file descriptors that were signalled. + */ + bool pollOnce(int timeoutMillis); + + /** + * Wakes the loop asynchronously. + * + * This method can be called on any thread. + * This method returns immediately. + */ + void wake(); + + /** + * Sets the callback for a file descriptor, replacing the existing one, if any. + * It is an error to call this method with events == 0 or callback == NULL. + * + * Note that a callback can be invoked with the POLLERR, POLLHUP or POLLNVAL events + * even if it is not explicitly requested when registered. + * + * This method can be called on any thread. + * This method may block briefly if it needs to wake the poll loop. + */ + void setCallback(int fd, int events, Callback callback, void* data = NULL); + + /** + * Removes the callback for a file descriptor, if one exists. + * + * When this method returns, it is safe to close the file descriptor since the poll loop + * will no longer have a reference to it. However, it is possible for the callback to + * already be running or for it to run one last time if the file descriptor was already + * signalled. Calling code is responsible for ensuring that this case is safely handled. + * For example, if the callback takes care of removing itself during its own execution either + * by returning false or calling this method, then it can be guaranteed to not be invoked + * again at any later time unless registered anew. + * + * This method can be called on any thread. + * This method may block briefly if it needs to wake the poll loop. + * + * Returns true if a callback was actually removed, false if none was registered. + */ + bool removeCallback(int fd); + +private: + struct RequestedCallback { + Callback callback; + void* data; + }; + + struct PendingCallback { + int fd; + int events; + Callback callback; + void* data; + }; + + Mutex mLock; + Condition mAwake; + bool mPolling; + + int mWakeReadPipeFd; + int mWakeWritePipeFd; + + Vector<struct pollfd> mRequestedFds; + Vector<RequestedCallback> mRequestedCallbacks; + + Vector<PendingCallback> mPendingCallbacks; // used privately by pollOnce + + void openWakePipe(); + void closeWakePipe(); + + ssize_t getRequestIndexLocked(int fd); + void wakeAndLock(); +}; + +} // namespace android + +#endif // UTILS_POLL_LOOP_H diff --git a/include/utils/Pool.h b/include/utils/Pool.h new file mode 100644 index 0000000..2ee768e --- /dev/null +++ b/include/utils/Pool.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#ifndef UTILS_POOL_H +#define UTILS_POOL_H + +#include <utils/TypeHelpers.h> + +namespace android { + +class PoolImpl { +public: + PoolImpl(size_t objSize); + ~PoolImpl(); + + void* allocImpl(); + void freeImpl(void* obj); + +private: + size_t mObjSize; +}; + +/* + * A homogeneous typed memory pool for fixed size objects. + * Not intended to be thread-safe. + */ +template<typename T> +class Pool : private PoolImpl { +public: + /* Creates an initially empty pool. */ + Pool() : PoolImpl(sizeof(T)) { } + + /* Destroys the pool. + * Assumes that the pool is empty. */ + ~Pool() { } + + /* Allocates an object from the pool, growing the pool if needed. */ + inline T* alloc() { + void* mem = allocImpl(); + if (! traits<T>::has_trivial_ctor) { + return new (mem) T(); + } else { + return static_cast<T*>(mem); + } + } + + /* Frees an object from the pool. */ + inline void free(T* obj) { + if (! traits<T>::has_trivial_dtor) { + obj->~T(); + } + freeImpl(obj); + } +}; + +} // namespace android + +#endif // UTILS_POOL_H diff --git a/include/utils/StopWatch.h b/include/utils/StopWatch.h index cc0bebc..693dd3c 100644 --- a/include/utils/StopWatch.h +++ b/include/utils/StopWatch.h @@ -37,6 +37,8 @@ public: const char* name() const; nsecs_t lap(); nsecs_t elapsedTime() const; + + void reset(); private: const char* mName; diff --git a/include/utils/Vector.h b/include/utils/Vector.h index ad59fd6..d40ae16 100644 --- a/include/utils/Vector.h +++ b/include/utils/Vector.h @@ -114,6 +114,12 @@ public: ssize_t appendVector(const Vector<TYPE>& vector); + //! insert an array at a given index + ssize_t insertArrayAt(const TYPE* array, size_t index, size_t numItems); + + //! append an array at the end of this vector + ssize_t appendArray(const TYPE* array, size_t numItems); + /*! * add/insert/replace items */ @@ -259,6 +265,16 @@ ssize_t Vector<TYPE>::appendVector(const Vector<TYPE>& vector) { } template<class TYPE> inline +ssize_t Vector<TYPE>::insertArrayAt(const TYPE* array, size_t index, size_t numItems) { + return VectorImpl::insertAt(array, index, numItems); +} + +template<class TYPE> inline +ssize_t Vector<TYPE>::appendArray(const TYPE* array, size_t numItems) { + return VectorImpl::add(array, numItems); +} + +template<class TYPE> inline ssize_t Vector<TYPE>::insertAt(const TYPE& item, size_t index, size_t numItems) { return VectorImpl::insertAt(&item, index, numItems); } diff --git a/include/utils/VectorImpl.h b/include/utils/VectorImpl.h index 49b03f1..46a7bc2 100644 --- a/include/utils/VectorImpl.h +++ b/include/utils/VectorImpl.h @@ -76,7 +76,7 @@ public: void push(); void push(const void* item); ssize_t add(); - ssize_t add(const void* item); + ssize_t add(const void* item, size_t numItems = 1); ssize_t replaceAt(size_t index); ssize_t replaceAt(const void* item, size_t index); @@ -184,6 +184,8 @@ private: void push(const void* item); ssize_t insertVectorAt(const VectorImpl& vector, size_t index); ssize_t appendVector(const VectorImpl& vector); + ssize_t insertArrayAt(const void* array, size_t index, size_t numItems); + ssize_t appendArray(const void* array, size_t numItems); ssize_t insertAt(size_t where, size_t numItems = 1); ssize_t insertAt(const void* item, size_t where, size_t numItems = 1); ssize_t replaceAt(size_t index); diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk index f7acd97..24cdc78 100644 --- a/libs/ui/Android.mk +++ b/libs/ui/Android.mk @@ -11,6 +11,11 @@ LOCAL_SRC_FILES:= \ GraphicBufferMapper.cpp \ KeyLayoutMap.cpp \ KeyCharacterMap.cpp \ + Input.cpp \ + InputDispatcher.cpp \ + InputManager.cpp \ + InputReader.cpp \ + InputTransport.cpp \ IOverlay.cpp \ Overlay.cpp \ PixelFormat.cpp \ diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp index d45eaf0..27895f2 100644 --- a/libs/ui/EventHub.cpp +++ b/libs/ui/EventHub.cpp @@ -155,77 +155,70 @@ int EventHub::getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue, return 0; } -int EventHub::getSwitchState(int sw) const -{ -#ifdef EV_SW - if (sw >= 0 && sw <= SW_MAX) { - int32_t devid = mSwitches[sw]; - if (devid != 0) { - return getSwitchState(devid, sw); +int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t scanCode) const { + if (scanCode >= 0 && scanCode <= KEY_MAX) { + AutoMutex _l(mLock); + + if (deviceId == -1) { + for (int i = 0; i < mNumDevicesById; i++) { + device_t* device = mDevicesById[i].device; + if (device != NULL && (device->classes & deviceClasses) != 0) { + int32_t result = getScanCodeStateLocked(device, scanCode); + if (result >= KEY_STATE_DOWN) { + return result; + } + } + } + return KEY_STATE_UP; + } else { + device_t* device = getDevice(deviceId); + if (device != NULL) { + return getScanCodeStateLocked(device, scanCode); + } } } -#endif - return -1; + return KEY_STATE_UNKNOWN; } -int EventHub::getSwitchState(int32_t deviceId, int sw) const -{ -#ifdef EV_SW - AutoMutex _l(mLock); - device_t* device = getDevice(deviceId); - if (device == NULL) return -1; - - if (sw >= 0 && sw <= SW_MAX) { - uint8_t sw_bitmask[(SW_MAX+7)/8]; - memset(sw_bitmask, 0, sizeof(sw_bitmask)); - if (ioctl(mFDs[id_to_index(device->id)].fd, - EVIOCGSW(sizeof(sw_bitmask)), sw_bitmask) >= 0) { - return test_bit(sw, sw_bitmask) ? 1 : 0; - } +int32_t EventHub::getScanCodeStateLocked(device_t* device, int32_t scanCode) const { + uint8_t key_bitmask[(KEY_MAX + 7) / 8]; + memset(key_bitmask, 0, sizeof(key_bitmask)); + if (ioctl(mFDs[id_to_index(device->id)].fd, + EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { + return test_bit(scanCode, key_bitmask) ? KEY_STATE_DOWN : KEY_STATE_UP; } -#endif - - return -1; + return KEY_STATE_UNKNOWN; } -int EventHub::getScancodeState(int code) const -{ - return getScancodeState(mFirstKeyboardId, code); -} +int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t deviceClasses, + int32_t keyCode) const { -int EventHub::getScancodeState(int32_t deviceId, int code) const -{ - AutoMutex _l(mLock); - device_t* device = getDevice(deviceId); - if (device == NULL) return -1; - - if (code >= 0 && code <= KEY_MAX) { - uint8_t key_bitmask[(KEY_MAX+7)/8]; - memset(key_bitmask, 0, sizeof(key_bitmask)); - if (ioctl(mFDs[id_to_index(device->id)].fd, - EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { - return test_bit(code, key_bitmask) ? 1 : 0; + if (deviceId == -1) { + for (int i = 0; i < mNumDevicesById; i++) { + device_t* device = mDevicesById[i].device; + if (device != NULL && (device->classes & deviceClasses) != 0) { + int32_t result = getKeyCodeStateLocked(device, keyCode); + if (result >= KEY_STATE_DOWN) { + return result; + } + } + } + return KEY_STATE_UP; + } else { + device_t* device = getDevice(deviceId); + if (device != NULL) { + return getKeyCodeStateLocked(device, keyCode); } } - - return -1; -} - -int EventHub::getKeycodeState(int code) const -{ - return getKeycodeState(mFirstKeyboardId, code); + return KEY_STATE_UNKNOWN; } -int EventHub::getKeycodeState(int32_t deviceId, int code) const -{ - AutoMutex _l(mLock); - device_t* device = getDevice(deviceId); - if (device == NULL || device->layoutMap == NULL) return -1; - +int32_t EventHub::getKeyCodeStateLocked(device_t* device, int32_t keyCode) const { Vector<int32_t> scanCodes; - device->layoutMap->findScancodes(code, &scanCodes); - - uint8_t key_bitmask[(KEY_MAX+7)/8]; + device->layoutMap->findScancodes(keyCode, &scanCodes); + + uint8_t key_bitmask[(KEY_MAX + 7) / 8]; memset(key_bitmask, 0, sizeof(key_bitmask)); if (ioctl(mFDs[id_to_index(device->id)].fd, EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { @@ -239,12 +232,45 @@ int EventHub::getKeycodeState(int32_t deviceId, int code) const int32_t sc = scanCodes.itemAt(i); //LOGI("Code %d: down=%d", sc, test_bit(sc, key_bitmask)); if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, key_bitmask)) { - return 1; + return KEY_STATE_DOWN; } } + return KEY_STATE_UP; } - - return 0; + return KEY_STATE_UNKNOWN; +} + +int32_t EventHub::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const { +#ifdef EV_SW + if (sw >= 0 && sw <= SW_MAX) { + AutoMutex _l(mLock); + + if (deviceId == -1) { + deviceId = mSwitches[sw]; + if (deviceId == 0) { + return KEY_STATE_UNKNOWN; + } + } + + device_t* device = getDevice(deviceId); + if (device == NULL) { + return KEY_STATE_UNKNOWN; + } + + return getSwitchStateLocked(device, sw); + } +#endif + return KEY_STATE_UNKNOWN; +} + +int32_t EventHub::getSwitchStateLocked(device_t* device, int32_t sw) const { + uint8_t sw_bitmask[(SW_MAX + 7) / 8]; + memset(sw_bitmask, 0, sizeof(sw_bitmask)); + if (ioctl(mFDs[id_to_index(device->id)].fd, + EVIOCGSW(sizeof(sw_bitmask)), sw_bitmask) >= 0) { + return test_bit(sw, sw_bitmask) ? KEY_STATE_DOWN : KEY_STATE_UP; + } + return KEY_STATE_UNKNOWN; } status_t EventHub::scancodeToKeycode(int32_t deviceId, int scancode, @@ -309,9 +335,6 @@ bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType, status_t err; - fd_set readfds; - int maxFd = -1; - int cc; int i; int res; int pollres; @@ -457,7 +480,7 @@ bool EventHub::openPlatformInput(void) * Inspect the known devices to determine whether physical keys exist for the given * framework-domain key codes. */ -bool EventHub::hasKeys(size_t numCodes, int32_t* keyCodes, uint8_t* outFlags) { +bool EventHub::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) { outFlags[codeIndex] = 0; @@ -465,7 +488,8 @@ bool EventHub::hasKeys(size_t numCodes, int32_t* keyCodes, uint8_t* outFlags) { Vector<int32_t> scanCodes; for (int n = 0; (n < mFDCount) && (outFlags[codeIndex] == 0); n++) { if (mDevices[n]) { - status_t err = mDevices[n]->layoutMap->findScancodes(keyCodes[codeIndex], &scanCodes); + status_t err = mDevices[n]->layoutMap->findScancodes( + keyCodes[codeIndex], &scanCodes); if (!err) { // check the possible scan codes identified by the layout map against the // map of codes actually emitted by the driver @@ -618,11 +642,11 @@ int EventHub::open_device(const char *deviceName) //} for (int i=0; i<((BTN_MISC+7)/8); i++) { if (key_bitmask[i] != 0) { - device->classes |= CLASS_KEYBOARD; + device->classes |= INPUT_DEVICE_CLASS_KEYBOARD; break; } } - if ((device->classes & CLASS_KEYBOARD) != 0) { + if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) { device->keyBitmask = new uint8_t[sizeof(key_bitmask)]; if (device->keyBitmask != NULL) { memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask)); @@ -642,7 +666,7 @@ int EventHub::open_device(const char *deviceName) if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bitmask)), rel_bitmask) >= 0) { if (test_bit(REL_X, rel_bitmask) && test_bit(REL_Y, rel_bitmask)) { - device->classes |= CLASS_TRACKBALL; + device->classes |= INPUT_DEVICE_CLASS_TRACKBALL; } } } @@ -656,12 +680,12 @@ int EventHub::open_device(const char *deviceName) if (test_bit(ABS_MT_TOUCH_MAJOR, abs_bitmask) && test_bit(ABS_MT_POSITION_X, abs_bitmask) && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) { - device->classes |= CLASS_TOUCHSCREEN | CLASS_TOUCHSCREEN_MT; + device->classes |= INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT; // Is this an old style single-touch driver? } else if (test_bit(BTN_TOUCH, key_bitmask) && test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) { - device->classes |= CLASS_TOUCHSCREEN; + device->classes |= INPUT_DEVICE_CLASS_TOUCHSCREEN; } #ifdef EV_SW @@ -680,7 +704,7 @@ int EventHub::open_device(const char *deviceName) } #endif - if ((device->classes&CLASS_KEYBOARD) != 0) { + if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) { char tmpfn[sizeof(name)]; char keylayoutFilename[300]; @@ -723,7 +747,7 @@ int EventHub::open_device(const char *deviceName) // 'Q' key support = cheap test of whether this is an alpha-capable kbd if (hasKeycode(device, kKeyCodeQ)) { - device->classes |= CLASS_ALPHAKEY; + device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY; } // See if this has a DPAD. @@ -732,7 +756,7 @@ int EventHub::open_device(const char *deviceName) hasKeycode(device, kKeyCodeDpadLeft) && hasKeycode(device, kKeyCodeDpadRight) && hasKeycode(device, kKeyCodeDpadCenter)) { - device->classes |= CLASS_DPAD; + device->classes |= INPUT_DEVICE_CLASS_DPAD; } LOGI("New keyboard: device->id=0x%x devname='%s' propName='%s' keylayout='%s'\n", diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp new file mode 100644 index 0000000..d367708 --- /dev/null +++ b/libs/ui/Input.cpp @@ -0,0 +1,238 @@ +// +// Copyright 2010 The Android Open Source Project +// +// Provides a pipe-based transport for native events in the NDK. +// +#define LOG_TAG "Input" + +//#define LOG_NDEBUG 0 + +#include <ui/Input.h> + +namespace android { + +// class InputEvent + +void InputEvent::initialize(int32_t deviceId, int32_t nature) { + mDeviceId = deviceId; + mNature = nature; +} + +// class KeyEvent + +void KeyEvent::initialize( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t flags, + int32_t keyCode, + int32_t scanCode, + int32_t metaState, + int32_t repeatCount, + nsecs_t downTime, + nsecs_t eventTime) { + InputEvent::initialize(deviceId, nature); + mAction = action; + mFlags = flags; + mKeyCode = keyCode; + mScanCode = scanCode; + mMetaState = metaState; + mRepeatCount = repeatCount; + mDownTime = downTime; + mEventTime = eventTime; +} + +// class MotionEvent + +void MotionEvent::initialize( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t edgeFlags, + int32_t metaState, + float rawX, + float rawY, + float xPrecision, + float yPrecision, + nsecs_t downTime, + nsecs_t eventTime, + size_t pointerCount, + const int32_t* pointerIds, + const PointerCoords* pointerCoords) { + InputEvent::initialize(deviceId, nature); + mAction = action; + mEdgeFlags = edgeFlags; + mMetaState = metaState; + mRawX = rawX; + mRawY = rawY; + mXPrecision = xPrecision; + mYPrecision = yPrecision; + mDownTime = downTime; + mPointerIds.clear(); + mPointerIds.appendArray(pointerIds, pointerCount); + mSampleEventTimes.clear(); + mSamplePointerCoords.clear(); + addSample(eventTime, pointerCoords); +} + +void MotionEvent::addSample( + int64_t eventTime, + const PointerCoords* pointerCoords) { + mSampleEventTimes.push(eventTime); + mSamplePointerCoords.appendArray(pointerCoords, getPointerCount()); +} + +void MotionEvent::offsetLocation(float xOffset, float yOffset) { + if (xOffset != 0 || yOffset != 0) { + for (size_t i = 0; i < mSamplePointerCoords.size(); i++) { + PointerCoords& pointerCoords = mSamplePointerCoords.editItemAt(i); + pointerCoords.x += xOffset; + pointerCoords.y += yOffset; + } + } +} + +} // namespace android + +// NDK APIs + +using android::InputEvent; +using android::KeyEvent; +using android::MotionEvent; + +int32_t input_event_get_type(const input_event_t* event) { + return reinterpret_cast<const InputEvent*>(event)->getType(); +} + +int32_t input_event_get_device_id(const input_event_t* event) { + return reinterpret_cast<const InputEvent*>(event)->getDeviceId(); +} + +int32_t input_event_get_nature(const input_event_t* event) { + return reinterpret_cast<const InputEvent*>(event)->getNature(); +} + +int32_t key_event_get_action(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getAction(); +} + +int32_t key_event_get_flags(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getFlags(); +} + +int32_t key_event_get_key_code(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getKeyCode(); +} + +int32_t key_event_get_scan_code(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getScanCode(); +} + +int32_t key_event_get_meta_state(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getMetaState(); +} +int32_t key_event_get_repeat_count(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getRepeatCount(); +} + +int64_t key_event_get_down_time(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getDownTime(); +} + +int64_t key_event_get_event_time(const input_event_t* key_event) { + return reinterpret_cast<const KeyEvent*>(key_event)->getEventTime(); +} + +int32_t motion_event_get_action(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getAction(); +} + +int32_t motion_event_get_meta_state(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getMetaState(); +} + +int32_t motion_event_get_edge_flags(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getEdgeFlags(); +} + +int64_t motion_event_get_down_time(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getDownTime(); +} + +int64_t motion_event_get_event_time(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getEventTime(); +} + +float motion_event_get_x_precision(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getXPrecision(); +} + +float motion_event_get_y_precision(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getYPrecision(); +} + +size_t motion_event_get_pointer_count(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getPointerCount(); +} + +int32_t motion_event_get_pointer_id(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getPointerId(pointer_index); +} + +float motion_event_get_raw_x(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getRawX(); +} + +float motion_event_get_raw_y(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getRawY(); +} + +float motion_event_get_x(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getX(pointer_index); +} + +float motion_event_get_y(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getY(pointer_index); +} + +float motion_event_get_pressure(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getPressure(pointer_index); +} + +float motion_event_get_size(const input_event_t* motion_event, size_t pointer_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getSize(pointer_index); +} + +size_t motion_event_get_history_size(const input_event_t* motion_event) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistorySize(); +} + +int64_t motion_event_get_historical_event_time(input_event_t* motion_event, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalEventTime( + history_index); +} + +float motion_event_get_historical_x(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalX( + pointer_index, history_index); +} + +float motion_event_get_historical_y(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalY( + pointer_index, history_index); +} + +float motion_event_get_historical_pressure(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalPressure( + pointer_index, history_index); +} + +float motion_event_get_historical_size(input_event_t* motion_event, size_t pointer_index, + size_t history_index) { + return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalSize( + pointer_index, history_index); +} diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp new file mode 100644 index 0000000..8e907da --- /dev/null +++ b/libs/ui/InputDispatcher.cpp @@ -0,0 +1,1315 @@ +// +// Copyright 2010 The Android Open Source Project +// +// The input dispatcher. +// +#define LOG_TAG "InputDispatcher" + +//#define LOG_NDEBUG 0 + +// Log detailed debug messages about each inbound event notification to the dispatcher. +#define DEBUG_INBOUND_EVENT_DETAILS 1 + +// Log detailed debug messages about each outbound event processed by the dispatcher. +#define DEBUG_OUTBOUND_EVENT_DETAILS 1 + +// Log debug messages about batching. +#define DEBUG_BATCHING 1 + +// Log debug messages about the dispatch cycle. +#define DEBUG_DISPATCH_CYCLE 1 + +// Log debug messages about performance statistics. +#define DEBUG_PERFORMANCE_STATISTICS 1 + +#include <cutils/log.h> +#include <ui/InputDispatcher.h> + +#include <stddef.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <limits.h> +#include <poll.h> + +namespace android { + +// TODO, this needs to be somewhere else, perhaps in the policy +static inline bool isMovementKey(int32_t keyCode) { + return keyCode == KEYCODE_DPAD_UP + || keyCode == KEYCODE_DPAD_DOWN + || keyCode == KEYCODE_DPAD_LEFT + || keyCode == KEYCODE_DPAD_RIGHT; +} + +// --- InputDispatcher --- + +InputDispatcher::InputDispatcher(const sp<InputDispatchPolicyInterface>& policy) : + mPolicy(policy) { + mPollLoop = new PollLoop(); + + mInboundQueue.head.refCount = -1; + mInboundQueue.head.type = EventEntry::TYPE_SENTINEL; + mInboundQueue.head.eventTime = LONG_LONG_MIN; + + mInboundQueue.tail.refCount = -1; + mInboundQueue.tail.type = EventEntry::TYPE_SENTINEL; + mInboundQueue.tail.eventTime = LONG_LONG_MAX; + + mKeyRepeatState.lastKeyEntry = NULL; +} + +InputDispatcher::~InputDispatcher() { + resetKeyRepeatLocked(); + + while (mConnectionsByReceiveFd.size() != 0) { + unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel); + } + + for (EventEntry* entry = mInboundQueue.head.next; entry != & mInboundQueue.tail; ) { + EventEntry* next = entry->next; + mAllocator.releaseEventEntry(next); + entry = next; + } +} + +void InputDispatcher::dispatchOnce() { + bool allowKeyRepeat = mPolicy->allowKeyRepeat(); + + nsecs_t currentTime; + nsecs_t nextWakeupTime = LONG_LONG_MAX; + { // acquire lock + AutoMutex _l(mLock); + currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + + // Reset the key repeat timer whenever we disallow key events, even if the next event + // is not a key. This is to ensure that we abort a key repeat if the device is just coming + // out of sleep. + // XXX we should handle resetting input state coming out of sleep more generally elsewhere + if (! allowKeyRepeat) { + resetKeyRepeatLocked(); + } + + // Process timeouts for all connections and determine if there are any synchronous + // event dispatches pending. + bool hasPendingSyncTarget = false; + for (size_t i = 0; i < mActiveConnections.size(); ) { + Connection* connection = mActiveConnections.itemAt(i); + + nsecs_t connectionTimeoutTime = connection->nextTimeoutTime; + if (connectionTimeoutTime <= currentTime) { + bool deactivated = timeoutDispatchCycleLocked(currentTime, connection); + if (deactivated) { + // Don't increment i because the connection has been removed + // from mActiveConnections (hence, deactivated). + continue; + } + } + + if (connectionTimeoutTime < nextWakeupTime) { + nextWakeupTime = connectionTimeoutTime; + } + + if (connection->hasPendingSyncTarget()) { + hasPendingSyncTarget = true; + } + + i += 1; + } + + // If we don't have a pending sync target, then we can begin delivering a new event. + // (Otherwise we wait for dispatch to complete for that target.) + if (! hasPendingSyncTarget) { + if (mInboundQueue.isEmpty()) { + if (mKeyRepeatState.lastKeyEntry) { + if (currentTime >= mKeyRepeatState.nextRepeatTime) { + processKeyRepeatLocked(currentTime); + return; // dispatched once + } else { + if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) { + nextWakeupTime = mKeyRepeatState.nextRepeatTime; + } + } + } + } else { + // Inbound queue has at least one entry. Dequeue it and begin dispatching. + // Note that we do not hold the lock for this process because dispatching may + // involve making many callbacks. + EventEntry* entry = mInboundQueue.dequeueAtHead(); + + switch (entry->type) { + case EventEntry::TYPE_CONFIGURATION_CHANGED: { + ConfigurationChangedEntry* typedEntry = + static_cast<ConfigurationChangedEntry*>(entry); + processConfigurationChangedLocked(currentTime, typedEntry); + mAllocator.releaseConfigurationChangedEntry(typedEntry); + break; + } + + case EventEntry::TYPE_KEY: { + KeyEntry* typedEntry = static_cast<KeyEntry*>(entry); + processKeyLocked(currentTime, typedEntry); + mAllocator.releaseKeyEntry(typedEntry); + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* typedEntry = static_cast<MotionEntry*>(entry); + processMotionLocked(currentTime, typedEntry); + mAllocator.releaseMotionEntry(typedEntry); + break; + } + + default: + assert(false); + break; + } + return; // dispatched once + } + } + } // release lock + + // Wait for callback or timeout or wake. + nsecs_t timeout = nanoseconds_to_milliseconds(nextWakeupTime - currentTime); + int32_t timeoutMillis = timeout > INT_MAX ? -1 : timeout > 0 ? int32_t(timeout) : 0; + mPollLoop->pollOnce(timeoutMillis); +} + +void InputDispatcher::processConfigurationChangedLocked(nsecs_t currentTime, + ConfigurationChangedEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("processConfigurationChanged - eventTime=%lld, touchScreenConfig=%d, " + "keyboardConfig=%d, navigationConfig=%d", entry->eventTime, + entry->touchScreenConfig, entry->keyboardConfig, entry->navigationConfig); +#endif + + mPolicy->notifyConfigurationChanged(entry->eventTime, entry->touchScreenConfig, + entry->keyboardConfig, entry->navigationConfig); +} + +void InputDispatcher::processKeyLocked(nsecs_t currentTime, KeyEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("processKey - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, action=0x%x, " + "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld", + entry->eventTime, entry->deviceId, entry->nature, entry->policyFlags, entry->action, + entry->flags, entry->keyCode, entry->scanCode, entry->metaState, + entry->downTime); +#endif + + // TODO: Poke user activity. + + if (entry->action == KEY_EVENT_ACTION_DOWN) { + if (mKeyRepeatState.lastKeyEntry + && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) { + // We have seen two identical key downs in a row which indicates that the device + // driver is automatically generating key repeats itself. We take note of the + // repeat here, but we disable our own next key repeat timer since it is clear that + // we will not need to synthesize key repeats ourselves. + entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1; + resetKeyRepeatLocked(); + mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves + } else { + // Not a repeat. Save key down state in case we do see a repeat later. + resetKeyRepeatLocked(); + mKeyRepeatState.nextRepeatTime = entry->eventTime + mPolicy->getKeyRepeatTimeout(); + } + mKeyRepeatState.lastKeyEntry = entry; + entry->refCount += 1; + } else { + resetKeyRepeatLocked(); + } + + identifyInputTargetsAndDispatchKeyLocked(currentTime, entry); +} + +void InputDispatcher::processKeyRepeatLocked(nsecs_t currentTime) { + // 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. + + // Synthesize a key repeat after the repeat timeout expired. + // We reuse the previous key entry if otherwise unreferenced. + KeyEntry* entry = mKeyRepeatState.lastKeyEntry; + if (entry->refCount == 1) { + entry->repeatCount += 1; + } else { + KeyEntry* newEntry = mAllocator.obtainKeyEntry(); + newEntry->deviceId = entry->deviceId; + newEntry->nature = entry->nature; + newEntry->policyFlags = entry->policyFlags; + newEntry->action = entry->action; + newEntry->flags = entry->flags; + newEntry->keyCode = entry->keyCode; + newEntry->scanCode = entry->scanCode; + newEntry->metaState = entry->metaState; + newEntry->repeatCount = entry->repeatCount + 1; + + mKeyRepeatState.lastKeyEntry = newEntry; + mAllocator.releaseKeyEntry(entry); + + entry = newEntry; + } + entry->eventTime = currentTime; + entry->downTime = currentTime; + entry->policyFlags = 0; + + mKeyRepeatState.nextRepeatTime = currentTime + mPolicy->getKeyRepeatTimeout(); + +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, " + "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, " + "repeatCount=%d, downTime=%lld", + entry->eventTime, entry->deviceId, entry->nature, entry->policyFlags, + entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState, + entry->repeatCount, entry->downTime); +#endif + + identifyInputTargetsAndDispatchKeyLocked(currentTime, entry); +} + +void InputDispatcher::processMotionLocked(nsecs_t currentTime, MotionEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("processMotion - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, action=0x%x, " + "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld", + entry->eventTime, entry->deviceId, entry->nature, entry->policyFlags, entry->action, + entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision, + entry->downTime); + + // Print the most recent sample that we have available, this may change due to batching. + size_t sampleCount = 1; + MotionSample* sample = & entry->firstSample; + for (; sample->next != NULL; sample = sample->next) { + sampleCount += 1; + } + for (uint32_t i = 0; i < entry->pointerCount; i++) { + LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f", + i, entry->pointerIds[i], + sample->pointerCoords[i].x, + sample->pointerCoords[i].y, + sample->pointerCoords[i].pressure, + sample->pointerCoords[i].size); + } + + // Keep in mind that due to batching, it is possible for the number of samples actually + // dispatched to change before the application finally consumed them. + if (entry->action == MOTION_EVENT_ACTION_MOVE) { + LOGD(" ... Total movement samples currently batched %d ...", sampleCount); + } +#endif + + identifyInputTargetsAndDispatchMotionLocked(currentTime, entry); +} + +void InputDispatcher::identifyInputTargetsAndDispatchKeyLocked( + nsecs_t currentTime, KeyEntry* entry) { +#if DEBUG_DISPATCH_CYCLE + LOGD("identifyInputTargetsAndDispatchKey"); +#endif + + mReusableKeyEvent.initialize(entry->deviceId, entry->nature, entry->action, entry->flags, + entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount, + entry->downTime, entry->eventTime); + + mCurrentInputTargets.clear(); + mPolicy->getKeyEventTargets(& mReusableKeyEvent, entry->policyFlags, + mCurrentInputTargets); + + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); +} + +void InputDispatcher::identifyInputTargetsAndDispatchMotionLocked( + nsecs_t currentTime, MotionEntry* entry) { +#if DEBUG_DISPATCH_CYCLE + LOGD("identifyInputTargetsAndDispatchMotion"); +#endif + + mReusableMotionEvent.initialize(entry->deviceId, entry->nature, entry->action, + entry->edgeFlags, entry->metaState, + entry->firstSample.pointerCoords[0].x, entry->firstSample.pointerCoords[0].y, + entry->xPrecision, entry->yPrecision, + entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds, + entry->firstSample.pointerCoords); + + mCurrentInputTargets.clear(); + mPolicy->getMotionEventTargets(& mReusableMotionEvent, entry->policyFlags, + mCurrentInputTargets); + + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); +} + +void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, + EventEntry* eventEntry, bool resumeWithAppendedMotionSample) { +#if DEBUG_DISPATCH_CYCLE + LOGD("dispatchEventToCurrentInputTargets, " + "resumeWithAppendedMotionSample=%s", + resumeWithAppendedMotionSample ? "true" : "false"); +#endif + + for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { + const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i); + + ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey( + inputTarget.inputChannel->getReceivePipeFd()); + if (connectionIndex >= 0) { + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + prepareDispatchCycleLocked(currentTime, connection.get(), eventEntry, & inputTarget, + resumeWithAppendedMotionSample); + } else { + LOGW("Framework requested delivery of an input event to channel '%s' but it " + "is not registered with the input dispatcher.", + inputTarget.inputChannel->getName().string()); + } + } +} + +void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, Connection* connection, + EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ prepareDispatchCycle, flags=%d, timeout=%lldns, " + "xOffset=%f, yOffset=%f, resumeWithAppendedMotionSample=%s", + connection->getInputChannelName(), inputTarget->flags, inputTarget->timeout, + inputTarget->xOffset, inputTarget->yOffset, + resumeWithAppendedMotionSample ? "true" : "false"); +#endif + + // Skip this event if the connection status is not normal. + // We don't want to queue outbound events at all if the connection is broken or + // not responding. + if (connection->status != Connection::STATUS_NORMAL) { + LOGV("channel '%s' ~ Dropping event because the channel status is %s", + connection->status == Connection::STATUS_BROKEN ? "BROKEN" : "NOT RESPONDING"); + return; + } + + // Resume the dispatch cycle with a freshly appended motion sample. + // First we check that the last dispatch entry in the outbound queue is for the same + // motion event to which we appended the motion sample. If we find such a dispatch + // entry, and if it is currently in progress then we try to stream the new sample. + bool wasEmpty = connection->outboundQueue.isEmpty(); + + if (! wasEmpty && resumeWithAppendedMotionSample) { + DispatchEntry* motionEventDispatchEntry = + connection->findQueuedDispatchEntryForEvent(eventEntry); + if (motionEventDispatchEntry) { + // If the dispatch entry is not in progress, then we must be busy dispatching an + // earlier event. Not a problem, the motion event is on the outbound queue and will + // be dispatched later. + if (! motionEventDispatchEntry->inProgress) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Not streaming because the motion event has " + "not yet been dispatched. " + "(Waiting for earlier events to be consumed.)", + connection->getInputChannelName()); +#endif + return; + } + + // If the dispatch entry is in progress but it already has a tail of pending + // motion samples, then it must mean that the shared memory buffer filled up. + // Not a problem, when this dispatch cycle is finished, we will eventually start + // a new dispatch cycle to process the tail and that tail includes the newly + // appended motion sample. + if (motionEventDispatchEntry->tailMotionSample) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Not streaming because no new samples can " + "be appended to the motion event in this dispatch cycle. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); +#endif + return; + } + + // The dispatch entry is in progress and is still potentially open for streaming. + // Try to stream the new motion sample. This might fail if the consumer has already + // consumed the motion event (or if the channel is broken). + MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample; + status_t status = connection->inputPublisher.appendMotionSample( + appendedMotionSample->eventTime, appendedMotionSample->pointerCoords); + if (status == OK) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Successfully streamed new motion sample.", + connection->getInputChannelName()); +#endif + return; + } + +#if DEBUG_BATCHING + if (status == NO_MEMORY) { + LOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatched move event because the shared memory buffer is full. " + "(Waiting for next dispatch cycle to start.)", + 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. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); + } else { + LOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatched move event due to an error, status=%d. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName(), status); + } +#endif + // Failed to stream. Start a new tail of pending motion samples to dispatch + // in the next cycle. + motionEventDispatchEntry->tailMotionSample = appendedMotionSample; + return; + } + } + + // This is a new event. + // Enqueue a new dispatch entry onto the outbound queue for this connection. + DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry); // increments ref + dispatchEntry->targetFlags = inputTarget->flags; + dispatchEntry->xOffset = inputTarget->xOffset; + dispatchEntry->yOffset = inputTarget->yOffset; + dispatchEntry->timeout = inputTarget->timeout; + dispatchEntry->inProgress = false; + dispatchEntry->headMotionSample = NULL; + dispatchEntry->tailMotionSample = NULL; + + // Handle the case where we could not stream a new motion sample because the consumer has + // already consumed the motion event (otherwise the corresponding dispatch entry would + // still be in the outbound queue for this connection). We set the head motion sample + // to the list starting with the newly appended motion sample. + if (resumeWithAppendedMotionSample) { +#if DEBUG_BATCHING + LOGD("channel '%s' ~ Preparing a new dispatch cycle for additional motion samples " + "that cannot be streamed because the motion event has already been consumed.", + connection->getInputChannelName()); +#endif + MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample; + dispatchEntry->headMotionSample = appendedMotionSample; + } + + // Enqueue the dispatch entry. + connection->outboundQueue.enqueueAtTail(dispatchEntry); + + // If the outbound queue was previously empty, start the dispatch cycle going. + if (wasEmpty) { + activateConnectionLocked(connection); + startDispatchCycleLocked(currentTime, connection); + } +} + +void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, Connection* connection) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ startDispatchCycle", + connection->getInputChannelName()); +#endif + + assert(connection->status == Connection::STATUS_NORMAL); + assert(! connection->outboundQueue.isEmpty()); + + DispatchEntry* dispatchEntry = connection->outboundQueue.head.next; + assert(! dispatchEntry->inProgress); + + // TODO throttle successive ACTION_MOVE motion events for the same device + // possible implementation could set a brief poll timeout here and resume starting the + // dispatch cycle when elapsed + + // Publish the event. + status_t status; + switch (dispatchEntry->eventEntry->type) { + case EventEntry::TYPE_KEY: { + KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry); + + // Apply target flags. + int32_t action = keyEntry->action; + int32_t flags = keyEntry->flags; + if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) { + flags |= KEY_EVENT_FLAG_CANCELED; + } + + // Publish the key event. + status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->nature, + action, flags, keyEntry->keyCode, keyEntry->scanCode, + keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime, + keyEntry->eventTime); + + if (status) { + LOGE("channel '%s' ~ Could not publish key event, " + "status=%d", connection->getInputChannelName(), status); + abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + return; + } + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry); + + // Apply target flags. + int32_t action = motionEntry->action; + if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) { + action = MOTION_EVENT_ACTION_OUTSIDE; + } + if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) { + action = MOTION_EVENT_ACTION_CANCEL; + } + + // If headMotionSample is non-NULL, then it points to the first new sample that we + // were unable to dispatch during the previous cycle so we resume dispatching from + // that point in the list of motion samples. + // Otherwise, we just start from the first sample of the motion event. + MotionSample* firstMotionSample = dispatchEntry->headMotionSample; + if (! firstMotionSample) { + firstMotionSample = & motionEntry->firstSample; + } + + // Publish the motion event and the first motion sample. + status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId, + motionEntry->nature, action, motionEntry->edgeFlags, motionEntry->metaState, + dispatchEntry->xOffset, dispatchEntry->yOffset, + motionEntry->xPrecision, motionEntry->yPrecision, + motionEntry->downTime, firstMotionSample->eventTime, + motionEntry->pointerCount, motionEntry->pointerIds, + firstMotionSample->pointerCoords); + + if (status) { + LOGE("channel '%s' ~ Could not publish motion event, " + "status=%d", connection->getInputChannelName(), status); + abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + return; + } + + // Append additional motion samples. + MotionSample* nextMotionSample = firstMotionSample->next; + for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) { + status = connection->inputPublisher.appendMotionSample( + nextMotionSample->eventTime, nextMotionSample->pointerCoords); + if (status == NO_MEMORY) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Shared memory buffer full. Some motion samples will " + "be sent in the next dispatch cycle.", + connection->getInputChannelName()); +#endif + break; + } + if (status != OK) { + LOGE("channel '%s' ~ Could not append motion sample " + "for a reason other than out of memory, status=%d", + connection->getInputChannelName(), status); + abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + return; + } + } + + // Remember the next motion sample that we could not dispatch, in case we ran out + // of space in the shared memory buffer. + dispatchEntry->tailMotionSample = nextMotionSample; + break; + } + + default: { + assert(false); + } + } + + // Send the dispatch signal. + status = connection->inputPublisher.sendDispatchSignal(); + if (status) { + LOGE("channel '%s' ~ Could not send dispatch signal, status=%d", + connection->getInputChannelName(), status); + abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + return; + } + + // Record information about the newly started dispatch cycle. + dispatchEntry->inProgress = true; + + connection->lastEventTime = dispatchEntry->eventEntry->eventTime; + connection->lastDispatchTime = currentTime; + + nsecs_t timeout = dispatchEntry->timeout; + connection->nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX; + + // Notify other system components. + onDispatchCycleStartedLocked(currentTime, connection); +} + +void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, Connection* connection) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ finishDispatchCycle: %01.1fms since event, " + "%01.1fms since dispatch", + connection->getInputChannelName(), + connection->getEventLatencyMillis(currentTime), + connection->getDispatchLatencyMillis(currentTime)); +#endif + + if (connection->status == Connection::STATUS_BROKEN) { + return; + } + + // Clear the pending timeout. + connection->nextTimeoutTime = LONG_LONG_MAX; + + if (connection->status == Connection::STATUS_NOT_RESPONDING) { + // Recovering from an ANR. + connection->status = Connection::STATUS_NORMAL; + + // Notify other system components. + onDispatchCycleFinishedLocked(currentTime, connection, true /*recoveredFromANR*/); + } else { + // Normal finish. Not much to do here. + + // Notify other system components. + onDispatchCycleFinishedLocked(currentTime, connection, false /*recoveredFromANR*/); + } + + // Reset the publisher since the event has been consumed. + // We do this now so that the publisher can release some of its internal resources + // while waiting for the next dispatch cycle to begin. + status_t status = connection->inputPublisher.reset(); + if (status) { + LOGE("channel '%s' ~ Could not reset publisher, status=%d", + connection->getInputChannelName(), status); + abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + return; + } + + // Start the next dispatch cycle for this connection. + while (! connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.head.next; + if (dispatchEntry->inProgress) { + // Finish or resume current event in progress. + if (dispatchEntry->tailMotionSample) { + // We have a tail of undispatched motion samples. + // Reuse the same DispatchEntry and start a new cycle. + dispatchEntry->inProgress = false; + dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample; + dispatchEntry->tailMotionSample = NULL; + startDispatchCycleLocked(currentTime, connection); + return; + } + // Finished. + connection->outboundQueue.dequeueAtHead(); + mAllocator.releaseDispatchEntry(dispatchEntry); + } else { + // If the head is not in progress, then we must have already dequeued the in + // progress event, which means we actually aborted it (due to ANR). + // So just start the next event for this connection. + startDispatchCycleLocked(currentTime, connection); + return; + } + } + + // Outbound queue is empty, deactivate the connection. + deactivateConnectionLocked(connection); +} + +bool InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime, Connection* connection) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ timeoutDispatchCycle", + connection->getInputChannelName()); +#endif + + if (connection->status != Connection::STATUS_NORMAL) { + return false; + } + + // Enter the not responding state. + connection->status = Connection::STATUS_NOT_RESPONDING; + connection->lastANRTime = currentTime; + bool deactivated = abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/); + + // Notify other system components. + onDispatchCycleANRLocked(currentTime, connection); + return deactivated; +} + +bool InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime, Connection* connection, + bool broken) { +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ abortDispatchCycle, broken=%s", + connection->getInputChannelName(), broken ? "true" : "false"); +#endif + + if (connection->status == Connection::STATUS_BROKEN) { + return false; + } + + // Clear the pending timeout. + connection->nextTimeoutTime = LONG_LONG_MAX; + + // Clear the outbound queue. + while (! connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead(); + mAllocator.releaseDispatchEntry(dispatchEntry); + } + + // Outbound queue is empty, deactivate the connection. + deactivateConnectionLocked(connection); + + // Handle the case where the connection appears to be unrecoverably broken. + if (broken) { + connection->status = Connection::STATUS_BROKEN; + + // Notify other system components. + onDispatchCycleBrokenLocked(currentTime, connection); + } + return true; /*deactivated*/ +} + +bool InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) { + InputDispatcher* d = static_cast<InputDispatcher*>(data); + + { // acquire lock + AutoMutex _l(d->mLock); + + ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + LOGE("Received spurious receive callback for unknown input channel. " + "fd=%d, events=0x%x", receiveFd, events); + return false; // remove the callback + } + + nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + + sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex); + if (events & (POLLERR | POLLHUP | POLLNVAL)) { + LOGE("channel '%s' ~ Consumer closed input channel or an error occurred. " + "events=0x%x", connection->getInputChannelName(), events); + d->abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/); + return false; // remove the callback + } + + if (! (events & POLLIN)) { + LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " + "events=0x%x", connection->getInputChannelName(), events); + return true; + } + + status_t status = connection->inputPublisher.receiveFinishedSignal(); + if (status) { + LOGE("channel '%s' ~ Failed to receive finished signal. status=%d", + connection->getInputChannelName(), status); + d->abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/); + return false; // remove the callback + } + + d->finishDispatchCycleLocked(currentTime, connection.get()); + return true; + } // release lock +} + +void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime, int32_t touchScreenConfig, + int32_t keyboardConfig, int32_t navigationConfig) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyConfigurationChanged - eventTime=%lld, touchScreenConfig=%d, " + "keyboardConfig=%d, navigationConfig=%d", eventTime, + touchScreenConfig, keyboardConfig, navigationConfig); +#endif + + bool wasEmpty; + { // acquire lock + AutoMutex _l(mLock); + + ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(); + newEntry->eventTime = eventTime; + newEntry->touchScreenConfig = touchScreenConfig; + newEntry->keyboardConfig = keyboardConfig; + newEntry->navigationConfig = navigationConfig; + + wasEmpty = mInboundQueue.isEmpty(); + mInboundQueue.enqueueAtTail(newEntry); + } // release lock + + if (wasEmpty) { + mPollLoop->wake(); + } +} + +void InputDispatcher::notifyLidSwitchChanged(nsecs_t eventTime, bool lidOpen) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyLidSwitchChanged - eventTime=%lld, open=%s", eventTime, + lidOpen ? "true" : "false"); +#endif + + // Send lid switch notification immediately and synchronously. + mPolicy->notifyLidSwitchChanged(eventTime, lidOpen); +} + +void InputDispatcher::notifyAppSwitchComing(nsecs_t eventTime) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyAppSwitchComing - eventTime=%lld", eventTime); +#endif + + // Remove movement keys from the queue from most recent to least recent, stopping at the + // first non-movement key. + // TODO: Include a detailed description of why we do this... + + { // acquire lock + AutoMutex _l(mLock); + + for (EventEntry* entry = mInboundQueue.tail.prev; entry != & mInboundQueue.head; ) { + EventEntry* prev = entry->prev; + + if (entry->type == EventEntry::TYPE_KEY) { + KeyEntry* keyEntry = static_cast<KeyEntry*>(entry); + if (isMovementKey(keyEntry->keyCode)) { + LOGV("Dropping movement key during app switch: keyCode=%d, action=%d", + keyEntry->keyCode, keyEntry->action); + mInboundQueue.dequeue(keyEntry); + mAllocator.releaseKeyEntry(keyEntry); + } else { + // stop at last non-movement key + break; + } + } + + entry = prev; + } + } // release lock +} + +void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t nature, + uint32_t policyFlags, int32_t action, int32_t flags, + int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyKey - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, action=0x%x, " + "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld", + eventTime, deviceId, nature, policyFlags, action, flags, + keyCode, scanCode, metaState, downTime); +#endif + + bool wasEmpty; + { // acquire lock + AutoMutex _l(mLock); + + KeyEntry* newEntry = mAllocator.obtainKeyEntry(); + newEntry->eventTime = eventTime; + newEntry->deviceId = deviceId; + newEntry->nature = nature; + newEntry->policyFlags = policyFlags; + newEntry->action = action; + newEntry->flags = flags; + newEntry->keyCode = keyCode; + newEntry->scanCode = scanCode; + newEntry->metaState = metaState; + newEntry->repeatCount = 0; + newEntry->downTime = downTime; + + wasEmpty = mInboundQueue.isEmpty(); + mInboundQueue.enqueueAtTail(newEntry); + } // release lock + + if (wasEmpty) { + mPollLoop->wake(); + } +} + +void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t nature, + uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags, + uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords, + float xPrecision, float yPrecision, nsecs_t downTime) { +#if DEBUG_INBOUND_EVENT_DETAILS + LOGD("notifyMotion - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, " + "action=0x%x, metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, " + "downTime=%lld", + eventTime, deviceId, nature, policyFlags, action, metaState, edgeFlags, + xPrecision, yPrecision, downTime); + for (uint32_t i = 0; i < pointerCount; i++) { + LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f", + i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y, + pointerCoords[i].pressure, pointerCoords[i].size); + } +#endif + + bool wasEmpty; + { // acquire lock + AutoMutex _l(mLock); + + // Attempt batching and streaming of move events. + if (action == MOTION_EVENT_ACTION_MOVE) { + // BATCHING CASE + // + // Try to append a move sample to the tail of the inbound queue for this device. + // Give up if we encounter a non-move motion event for this device since that + // means we cannot append any new samples until a new motion event has started. + for (EventEntry* entry = mInboundQueue.tail.prev; + entry != & mInboundQueue.head; entry = entry->prev) { + if (entry->type != EventEntry::TYPE_MOTION) { + // Keep looking for motion events. + continue; + } + + MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); + if (motionEntry->deviceId != deviceId) { + // Keep looking for this device. + continue; + } + + if (motionEntry->action != MOTION_EVENT_ACTION_MOVE + || motionEntry->pointerCount != pointerCount) { + // Last motion event in the queue for this device is not compatible for + // appending new samples. Stop here. + goto NoBatchingOrStreaming; + } + + // The last motion event is a move and is compatible for appending. + // Do the batching magic and exit. + mAllocator.appendMotionSample(motionEntry, eventTime, pointerCount, pointerCoords); +#if DEBUG_BATCHING + LOGD("Appended motion sample onto batch for most recent " + "motion event for this device in the inbound queue."); +#endif + return; // done + } + + // STREAMING CASE + // + // There is no pending motion event (of any kind) for this device in the inbound queue. + // Search the outbound queues for a synchronously dispatched motion event for this + // device. If found, then we append the new sample to that event and then try to + // push it out to all current targets. It is possible that some targets will already + // have consumed the motion event. This case is automatically handled by the + // logic in prepareDispatchCycleLocked by tracking where resumption takes place. + // + // The reason we look for a synchronously dispatched motion event is because we + // want to be sure that no other motion events have been dispatched since the move. + // It's also convenient because it means that the input targets are still valid. + // This code could be improved to support streaming of asynchronously dispatched + // motion events (which might be significantly more efficient) but it may become + // a little more complicated as a result. + // + // Note: This code crucially depends on the invariant that an outbound queue always + // contains at most one synchronous event and it is always last (but it might + // not be first!). + for (size_t i = 0; i < mActiveConnections.size(); i++) { + Connection* connection = mActiveConnections.itemAt(i); + if (! connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev; + if (dispatchEntry->targetFlags & InputTarget::FLAG_SYNC) { + if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) { + goto NoBatchingOrStreaming; + } + + MotionEntry* syncedMotionEntry = static_cast<MotionEntry*>( + dispatchEntry->eventEntry); + if (syncedMotionEntry->action != MOTION_EVENT_ACTION_MOVE + || syncedMotionEntry->deviceId != deviceId + || syncedMotionEntry->pointerCount != pointerCount) { + goto NoBatchingOrStreaming; + } + + // Found synced move entry. Append sample and resume dispatch. + mAllocator.appendMotionSample(syncedMotionEntry, eventTime, + pointerCount, pointerCoords); +#if DEBUG_BATCHING + LOGD("Appended motion sample onto batch for most recent synchronously " + "dispatched motion event for this device in the outbound queues."); +#endif + nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + dispatchEventToCurrentInputTargetsLocked(currentTime, syncedMotionEntry, + true /*resumeWithAppendedMotionSample*/); + return; // done! + } + } + } + +NoBatchingOrStreaming:; + } + + // Just enqueue a new motion event. + MotionEntry* newEntry = mAllocator.obtainMotionEntry(); + newEntry->eventTime = eventTime; + newEntry->deviceId = deviceId; + newEntry->nature = nature; + newEntry->policyFlags = policyFlags; + newEntry->action = action; + newEntry->metaState = metaState; + newEntry->edgeFlags = edgeFlags; + newEntry->xPrecision = xPrecision; + newEntry->yPrecision = yPrecision; + newEntry->downTime = downTime; + newEntry->pointerCount = pointerCount; + newEntry->firstSample.eventTime = eventTime; + newEntry->lastSample = & newEntry->firstSample; + for (uint32_t i = 0; i < pointerCount; i++) { + newEntry->pointerIds[i] = pointerIds[i]; + newEntry->firstSample.pointerCoords[i] = pointerCoords[i]; + } + + wasEmpty = mInboundQueue.isEmpty(); + mInboundQueue.enqueueAtTail(newEntry); + } // release lock + + if (wasEmpty) { + mPollLoop->wake(); + } +} + +void InputDispatcher::resetKeyRepeatLocked() { + if (mKeyRepeatState.lastKeyEntry) { + mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry); + mKeyRepeatState.lastKeyEntry = NULL; + } +} + +status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) { + int receiveFd; + { // acquire lock + AutoMutex _l(mLock); + + receiveFd = inputChannel->getReceivePipeFd(); + if (mConnectionsByReceiveFd.indexOfKey(receiveFd) >= 0) { + LOGW("Attempted to register already registered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + sp<Connection> connection = new Connection(inputChannel); + status_t status = connection->initialize(); + if (status) { + LOGE("Failed to initialize input publisher for input channel '%s', status=%d", + inputChannel->getName().string(), status); + return status; + } + + mConnectionsByReceiveFd.add(receiveFd, connection); + } // release lock + + mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this); + return OK; +} + +status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) { + int32_t receiveFd; + { // acquire lock + AutoMutex _l(mLock); + + receiveFd = inputChannel->getReceivePipeFd(); + ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + LOGW("Attempted to unregister already unregistered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + mConnectionsByReceiveFd.removeItemsAt(connectionIndex); + + connection->status = Connection::STATUS_ZOMBIE; + + nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); + abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/); + } // release lock + + mPollLoop->removeCallback(receiveFd); + + // Wake the poll loop because removing the connection may have changed the current + // synchronization state. + mPollLoop->wake(); + return OK; +} + +void InputDispatcher::activateConnectionLocked(Connection* connection) { + for (size_t i = 0; i < mActiveConnections.size(); i++) { + if (mActiveConnections.itemAt(i) == connection) { + return; + } + } + mActiveConnections.add(connection); +} + +void InputDispatcher::deactivateConnectionLocked(Connection* connection) { + for (size_t i = 0; i < mActiveConnections.size(); i++) { + if (mActiveConnections.itemAt(i) == connection) { + mActiveConnections.removeAt(i); + return; + } + } +} + +void InputDispatcher::onDispatchCycleStartedLocked(nsecs_t currentTime, Connection* connection) { +} + +void InputDispatcher::onDispatchCycleFinishedLocked(nsecs_t currentTime, + Connection* connection, bool recoveredFromANR) { + if (recoveredFromANR) { + LOGI("channel '%s' ~ Recovered from ANR. %01.1fms since event, " + "%01.1fms since dispatch, %01.1fms since ANR", + connection->getInputChannelName(), + connection->getEventLatencyMillis(currentTime), + connection->getDispatchLatencyMillis(currentTime), + connection->getANRLatencyMillis(currentTime)); + + // TODO tell framework + } +} + +void InputDispatcher::onDispatchCycleANRLocked(nsecs_t currentTime, Connection* connection) { + LOGI("channel '%s' ~ Not responding! %01.1fms since event, %01.1fms since dispatch", + connection->getInputChannelName(), + connection->getEventLatencyMillis(currentTime), + connection->getDispatchLatencyMillis(currentTime)); + + // TODO tell framework +} + +void InputDispatcher::onDispatchCycleBrokenLocked(nsecs_t currentTime, Connection* connection) { + LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!", + connection->getInputChannelName()); + + // TODO tell framework +} + +// --- InputDispatcher::Allocator --- + +InputDispatcher::Allocator::Allocator() { +} + +InputDispatcher::ConfigurationChangedEntry* +InputDispatcher::Allocator::obtainConfigurationChangedEntry() { + ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc(); + entry->refCount = 1; + entry->type = EventEntry::TYPE_CONFIGURATION_CHANGED; + return entry; +} + +InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry() { + KeyEntry* entry = mKeyEntryPool.alloc(); + entry->refCount = 1; + entry->type = EventEntry::TYPE_KEY; + return entry; +} + +InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry() { + MotionEntry* entry = mMotionEntryPool.alloc(); + entry->refCount = 1; + entry->type = EventEntry::TYPE_MOTION; + entry->firstSample.next = NULL; + return entry; +} + +InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry( + EventEntry* eventEntry) { + DispatchEntry* entry = mDispatchEntryPool.alloc(); + entry->eventEntry = eventEntry; + eventEntry->refCount += 1; + return entry; +} + +void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) { + switch (entry->type) { + case EventEntry::TYPE_CONFIGURATION_CHANGED: + releaseConfigurationChangedEntry(static_cast<ConfigurationChangedEntry*>(entry)); + break; + case EventEntry::TYPE_KEY: + releaseKeyEntry(static_cast<KeyEntry*>(entry)); + break; + case EventEntry::TYPE_MOTION: + releaseMotionEntry(static_cast<MotionEntry*>(entry)); + break; + default: + assert(false); + break; + } +} + +void InputDispatcher::Allocator::releaseConfigurationChangedEntry( + ConfigurationChangedEntry* entry) { + entry->refCount -= 1; + if (entry->refCount == 0) { + mConfigurationChangeEntryPool.free(entry); + } else { + assert(entry->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) { + entry->refCount -= 1; + if (entry->refCount == 0) { + mKeyEntryPool.free(entry); + } else { + assert(entry->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) { + entry->refCount -= 1; + if (entry->refCount == 0) { + freeMotionSampleList(entry->firstSample.next); + mMotionEntryPool.free(entry); + } else { + assert(entry->refCount > 0); + } +} + +void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) { + releaseEventEntry(entry->eventEntry); + mDispatchEntryPool.free(entry); +} + +void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry, + nsecs_t eventTime, int32_t pointerCount, const PointerCoords* pointerCoords) { + MotionSample* sample = mMotionSamplePool.alloc(); + sample->eventTime = eventTime; + for (int32_t i = 0; i < pointerCount; i++) { + sample->pointerCoords[i] = pointerCoords[i]; + } + + sample->next = NULL; + motionEntry->lastSample->next = sample; + motionEntry->lastSample = sample; +} + +void InputDispatcher::Allocator::freeMotionSample(MotionSample* sample) { + mMotionSamplePool.free(sample); +} + +void InputDispatcher::Allocator::freeMotionSampleList(MotionSample* head) { + while (head) { + MotionSample* next = head->next; + mMotionSamplePool.free(head); + head = next; + } +} + +// --- InputDispatcher::Connection --- + +InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) : + status(STATUS_NORMAL), inputChannel(inputChannel), inputPublisher(inputChannel), + nextTimeoutTime(LONG_LONG_MAX), + lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX), + lastANRTime(LONG_LONG_MAX) { +} + +InputDispatcher::Connection::~Connection() { +} + +status_t InputDispatcher::Connection::initialize() { + return inputPublisher.initialize(); +} + +InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent( + const EventEntry* eventEntry) const { + for (DispatchEntry* dispatchEntry = outboundQueue.tail.prev; + dispatchEntry != & outboundQueue.head; dispatchEntry = dispatchEntry->prev) { + if (dispatchEntry->eventEntry == eventEntry) { + return dispatchEntry; + } + } + return NULL; +} + + +// --- InputDispatcherThread --- + +InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) : + Thread(/*canCallJava*/ true), mDispatcher(dispatcher) { +} + +InputDispatcherThread::~InputDispatcherThread() { +} + +bool InputDispatcherThread::threadLoop() { + mDispatcher->dispatchOnce(); + return true; +} + +} // namespace android diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp new file mode 100644 index 0000000..ab354a5 --- /dev/null +++ b/libs/ui/InputManager.cpp @@ -0,0 +1,114 @@ +// +// Copyright 2010 The Android Open Source Project +// +// The input manager. +// +#define LOG_TAG "InputManager" + +//#define LOG_NDEBUG 0 + +#include <cutils/log.h> +#include <ui/InputManager.h> +#include <ui/InputReader.h> +#include <ui/InputDispatcher.h> + +namespace android { + +InputManager::InputManager(const sp<EventHubInterface>& eventHub, + const sp<InputDispatchPolicyInterface>& policy) : + mEventHub(eventHub), mPolicy(policy) { + mDispatcher = new InputDispatcher(policy); + mReader = new InputReader(eventHub, policy, mDispatcher); + + mDispatcherThread = new InputDispatcherThread(mDispatcher); + mReaderThread = new InputReaderThread(mReader); + + configureExcludedDevices(); +} + +InputManager::~InputManager() { + stop(); +} + +void InputManager::configureExcludedDevices() { + Vector<String8> excludedDeviceNames; + mPolicy->getExcludedDeviceNames(excludedDeviceNames); + + for (size_t i = 0; i < excludedDeviceNames.size(); i++) { + mEventHub->addExcludedDevice(excludedDeviceNames[i]); + } +} + +status_t InputManager::start() { + status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY); + if (result) { + LOGE("Could not start InputDispatcher thread due to error %d.", result); + return result; + } + + result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); + if (result) { + LOGE("Could not start InputReader thread due to error %d.", result); + + mDispatcherThread->requestExit(); + return result; + } + + return OK; +} + +status_t InputManager::stop() { + status_t result = mReaderThread->requestExitAndWait(); + if (result) { + LOGW("Could not stop InputReader thread due to error %d.", result); + } + + result = mDispatcherThread->requestExitAndWait(); + if (result) { + LOGW("Could not stop InputDispatcher thread due to error %d.", result); + } + + return OK; +} + +status_t InputManager::registerInputChannel(const sp<InputChannel>& inputChannel) { + return mDispatcher->registerInputChannel(inputChannel); +} + +status_t InputManager::unregisterInputChannel(const sp<InputChannel>& inputChannel) { + return mDispatcher->unregisterInputChannel(inputChannel); +} + +int32_t InputManager::getScanCodeState(int32_t deviceId, int32_t deviceClasses, int32_t scanCode) + const { + int32_t vkKeyCode, vkScanCode; + if (mReader->getCurrentVirtualKey(& vkKeyCode, & vkScanCode)) { + if (vkScanCode == scanCode) { + return KEY_STATE_VIRTUAL; + } + } + + return mEventHub->getScanCodeState(deviceId, deviceClasses, scanCode); +} + +int32_t InputManager::getKeyCodeState(int32_t deviceId, int32_t deviceClasses, int32_t keyCode) + const { + int32_t vkKeyCode, vkScanCode; + if (mReader->getCurrentVirtualKey(& vkKeyCode, & vkScanCode)) { + if (vkKeyCode == keyCode) { + return KEY_STATE_VIRTUAL; + } + } + + return mEventHub->getKeyCodeState(deviceId, deviceClasses, keyCode); +} + +int32_t InputManager::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const { + return mEventHub->getSwitchState(deviceId, deviceClasses, sw); +} + +bool InputManager::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const { + return mEventHub->hasKeys(numCodes, keyCodes, outFlags); +} + +} // namespace android diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp new file mode 100644 index 0000000..76f9ec9 --- /dev/null +++ b/libs/ui/InputReader.cpp @@ -0,0 +1,1844 @@ +// +// Copyright 2010 The Android Open Source Project +// +// The input reader. +// +#define LOG_TAG "InputReader" + +//#define LOG_NDEBUG 0 + +// Log debug messages for each raw event received from the EventHub. +#define DEBUG_RAW_EVENTS 0 + +// Log debug messages about touch screen filtering hacks. +#define DEBUG_HACKS 1 + +// Log debug messages about virtual key processing. +#define DEBUG_VIRTUAL_KEYS 1 + +// Log debug messages about pointers. +#define DEBUG_POINTERS 1 + +#include <cutils/log.h> +#include <ui/InputReader.h> + +#include <stddef.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <limits.h> + +namespace android { + +// --- Static Functions --- + +template<typename T> +inline static T abs(const T& value) { + return value < 0 ? - value : value; +} + +template<typename T> +inline static T min(const T& a, const T& b) { + return a < b ? a : b; +} + +int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) { + int32_t mask; + switch (keyCode) { + case KEYCODE_ALT_LEFT: + mask = META_ALT_LEFT_ON; + break; + case KEYCODE_ALT_RIGHT: + mask = META_ALT_RIGHT_ON; + break; + case KEYCODE_SHIFT_LEFT: + mask = META_SHIFT_LEFT_ON; + break; + case KEYCODE_SHIFT_RIGHT: + mask = META_SHIFT_RIGHT_ON; + break; + case KEYCODE_SYM: + mask = META_SYM_ON; + break; + default: + return oldMetaState; + } + + int32_t newMetaState = down ? oldMetaState | mask : oldMetaState & ~ mask + & ~ (META_ALT_ON | META_SHIFT_ON); + + if (newMetaState & (META_ALT_LEFT_ON | META_ALT_RIGHT_ON)) { + newMetaState |= META_ALT_ON; + } + + if (newMetaState & (META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON)) { + newMetaState |= META_SHIFT_ON; + } + + return newMetaState; +} + +static const int32_t keyCodeRotationMap[][4] = { + // key codes enumerated counter-clockwise with the original (unrotated) key first + // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation + { KEYCODE_DPAD_DOWN, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_UP, KEYCODE_DPAD_LEFT }, + { KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_UP, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_DOWN }, + { KEYCODE_DPAD_UP, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_RIGHT }, + { KEYCODE_DPAD_LEFT, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_UP }, +}; +static const int keyCodeRotationMapSize = + sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]); + +int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) { + if (orientation != InputDispatchPolicyInterface::ROTATION_0) { + for (int i = 0; i < keyCodeRotationMapSize; i++) { + if (keyCode == keyCodeRotationMap[i][0]) { + return keyCodeRotationMap[i][orientation]; + } + } + } + return keyCode; +} + + +// --- InputDevice --- + +InputDevice::InputDevice(int32_t id, uint32_t classes, String8 name) : + id(id), classes(classes), name(name), ignored(false) { +} + +void InputDevice::reset() { + if (isKeyboard()) { + keyboard.reset(); + } + + if (isTrackball()) { + trackball.reset(); + } + + if (isMultiTouchScreen()) { + multiTouchScreen.reset(); + } else if (isSingleTouchScreen()) { + singleTouchScreen.reset(); + } + + if (isTouchScreen()) { + touchScreen.reset(); + } +} + + +// --- InputDevice::TouchData --- + +void InputDevice::TouchData::copyFrom(const TouchData& other) { + pointerCount = other.pointerCount; + idBits = other.idBits; + + for (uint32_t i = 0; i < pointerCount; i++) { + pointers[i] = other.pointers[i]; + idToIndex[i] = other.idToIndex[i]; + } +} + + +// --- InputDevice::KeyboardState --- + +void InputDevice::KeyboardState::reset() { + current.metaState = META_NONE; + current.downTime = 0; +} + + +// --- InputDevice::TrackballState --- + +void InputDevice::TrackballState::reset() { + accumulator.clear(); + current.down = false; + current.downTime = 0; +} + + +// --- InputDevice::TouchScreenState --- + +void InputDevice::TouchScreenState::reset() { + lastTouch.clear(); + downTime = 0; + currentVirtualKey.down = false; + + for (uint32_t i = 0; i < MAX_POINTERS; i++) { + averagingTouchFilter.historyStart[i] = 0; + averagingTouchFilter.historyEnd[i] = 0; + } + + jumpyTouchFilter.jumpyPointsDropped = 0; +} + +void InputDevice::TouchScreenState::calculatePointerIds() { + uint32_t currentPointerCount = currentTouch.pointerCount; + uint32_t lastPointerCount = lastTouch.pointerCount; + + if (currentPointerCount == 0) { + // No pointers to assign. + currentTouch.idBits.clear(); + } else if (lastPointerCount == 0) { + // All pointers are new. + currentTouch.idBits.clear(); + for (uint32_t i = 0; i < currentPointerCount; i++) { + currentTouch.pointers[i].id = i; + currentTouch.idToIndex[i] = i; + currentTouch.idBits.markBit(i); + } + } else if (currentPointerCount == 1 && lastPointerCount == 1) { + // Only one pointer and no change in count so it must have the same id as before. + uint32_t id = lastTouch.pointers[0].id; + currentTouch.pointers[0].id = id; + currentTouch.idToIndex[id] = 0; + currentTouch.idBits.value = BitSet32::valueForBit(id); + } else { + // General case. + // We build a heap of squared euclidean distances between current and last pointers + // associated with the current and last pointer indices. Then, we find the best + // match (by distance) for each current pointer. + struct { + uint32_t currentPointerIndex : 8; + uint32_t lastPointerIndex : 8; + uint64_t distance : 48; // squared distance + } heap[MAX_POINTERS * MAX_POINTERS]; + + uint32_t heapSize = 0; + for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount; + currentPointerIndex++) { + for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount; + lastPointerIndex++) { + int64_t deltaX = currentTouch.pointers[currentPointerIndex].x + - lastTouch.pointers[lastPointerIndex].x; + int64_t deltaY = currentTouch.pointers[currentPointerIndex].y + - lastTouch.pointers[lastPointerIndex].y; + + uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); + + // Insert new element into the heap (sift up). + heapSize += 1; + uint32_t insertionIndex = heapSize; + while (insertionIndex > 1) { + uint32_t parentIndex = (insertionIndex - 1) / 2; + if (distance < heap[parentIndex].distance) { + heap[insertionIndex] = heap[parentIndex]; + insertionIndex = parentIndex; + } else { + break; + } + } + heap[insertionIndex].currentPointerIndex = currentPointerIndex; + heap[insertionIndex].lastPointerIndex = lastPointerIndex; + heap[insertionIndex].distance = distance; + } + } + + // Pull matches out by increasing order of distance. + // To avoid reassigning pointers that have already been matched, the loop keeps track + // of which last and current pointers have been matched using the matchedXXXBits variables. + // It also tracks the used pointer id bits. + BitSet32 matchedLastBits(0); + BitSet32 matchedCurrentBits(0); + BitSet32 usedIdBits(0); + bool first = true; + for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) { + for (;;) { + if (first) { + // The first time through the loop, we just consume the root element of + // the heap (the one with smalled distance). + first = false; + } else { + // Previous iterations consumed the root element of the heap. + // Pop root element off of the heap (sift down). + heapSize -= 1; + assert(heapSize > 0); + + // Sift down to find where the element at index heapSize needs to be moved. + uint32_t rootIndex = 0; + for (;;) { + uint32_t childIndex = rootIndex * 2 + 1; + if (childIndex >= heapSize) { + break; + } + + if (childIndex + 1 < heapSize + && heap[childIndex + 1].distance < heap[childIndex].distance) { + childIndex += 1; + } + + if (heap[heapSize].distance < heap[childIndex].distance) { + break; + } + + heap[rootIndex] = heap[childIndex]; + rootIndex = childIndex; + } + heap[rootIndex] = heap[heapSize]; + } + + uint32_t currentPointerIndex = heap[0].currentPointerIndex; + if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched + + uint32_t lastPointerIndex = heap[0].lastPointerIndex; + if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched + + matchedCurrentBits.markBit(currentPointerIndex); + matchedLastBits.markBit(lastPointerIndex); + + uint32_t id = lastTouch.pointers[lastPointerIndex].id; + currentTouch.pointers[currentPointerIndex].id = id; + currentTouch.idToIndex[id] = currentPointerIndex; + usedIdBits.markBit(id); + break; + } + } + + // Assign fresh ids to new pointers. + if (currentPointerCount > lastPointerCount) { + for (uint32_t i = currentPointerCount - lastPointerCount; ;) { + uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit(); + uint32_t id = usedIdBits.firstUnmarkedBit(); + + currentTouch.pointers[currentPointerIndex].id = id; + currentTouch.idToIndex[id] = currentPointerIndex; + usedIdBits.markBit(id); + + if (--i == 0) break; // done + matchedCurrentBits.markBit(currentPointerIndex); + } + } + + // Fix id bits. + currentTouch.idBits = usedIdBits; + } +} + +/* Special hack for devices that have bad screen data: if one of the + * points has moved more than a screen height from the last position, + * then drop it. */ +bool InputDevice::TouchScreenState::applyBadTouchFilter() { + uint32_t pointerCount = currentTouch.pointerCount; + + // Nothing to do if there are no points. + if (pointerCount == 0) { + return false; + } + + // Don't do anything if a finger is going down or up. We run + // here before assigning pointer IDs, so there isn't a good + // way to do per-finger matching. + if (pointerCount != lastTouch.pointerCount) { + return false; + } + + // We consider a single movement across more than a 7/16 of + // the long size of the screen to be bad. This was a magic value + // determined by looking at the maximum distance it is feasible + // to actually move in one sample. + int32_t maxDeltaY = parameters.yAxis.range * 7 / 16; + + // XXX The original code in InputDevice.java included commented out + // code for testing the X axis. Note that when we drop a point + // we don't actually restore the old X either. Strange. + // The old code also tries to track when bad points were previously + // detected but it turns out that due to the placement of a "break" + // at the end of the loop, we never set mDroppedBadPoint to true + // so it is effectively dead code. + // Need to figure out if the old code is busted or just overcomplicated + // but working as intended. + + // Look through all new points and see if any are farther than + // acceptable from all previous points. + for (uint32_t i = pointerCount; i-- > 0; ) { + int32_t y = currentTouch.pointers[i].y; + int32_t closestY = INT_MAX; + int32_t closestDeltaY = 0; + +#if DEBUG_HACKS + LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y); +#endif + + for (uint32_t j = pointerCount; j-- > 0; ) { + int32_t lastY = lastTouch.pointers[j].y; + int32_t deltaY = abs(y - lastY); + +#if DEBUG_HACKS + LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d", + j, lastY, deltaY); +#endif + + if (deltaY < maxDeltaY) { + goto SkipSufficientlyClosePoint; + } + if (deltaY < closestDeltaY) { + closestDeltaY = deltaY; + closestY = lastY; + } + } + + // Must not have found a close enough match. +#if DEBUG_HACKS + LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d", + i, y, closestY, closestDeltaY, maxDeltaY); +#endif + + currentTouch.pointers[i].y = closestY; + return true; // XXX original code only corrects one point + + SkipSufficientlyClosePoint: ; + } + + // No change. + return false; +} + +/* Special hack for devices that have bad screen data: drop points where + * the coordinate value for one axis has jumped to the other pointer's location. + */ +bool InputDevice::TouchScreenState::applyJumpyTouchFilter() { + uint32_t pointerCount = currentTouch.pointerCount; + if (lastTouch.pointerCount != pointerCount) { +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Different pointer count %d -> %d", + lastTouch.pointerCount, pointerCount); + for (uint32_t i = 0; i < pointerCount; i++) { + LOGD(" Pointer %d (%d, %d)", i, + currentTouch.pointers[i].x, currentTouch.pointers[i].y); + } +#endif + + if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) { + if (lastTouch.pointerCount == 1 && pointerCount == 2) { + // Just drop the first few events going from 1 to 2 pointers. + // They're bad often enough that they're not worth considering. + currentTouch.pointerCount = 1; + jumpyTouchFilter.jumpyPointsDropped += 1; + +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Pointer 2 dropped"); +#endif + return true; + } else if (lastTouch.pointerCount == 2 && pointerCount == 1) { + // The event when we go from 2 -> 1 tends to be messed up too + currentTouch.pointerCount = 2; + currentTouch.pointers[0] = lastTouch.pointers[0]; + currentTouch.pointers[1] = lastTouch.pointers[1]; + jumpyTouchFilter.jumpyPointsDropped += 1; + +#if DEBUG_HACKS + for (int32_t i = 0; i < 2; i++) { + LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i, + currentTouch.pointers[i].x, currentTouch.pointers[i].y); + } +#endif + return true; + } + } + // Reset jumpy points dropped on other transitions or if limit exceeded. + jumpyTouchFilter.jumpyPointsDropped = 0; + +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Transition - drop limit reset"); +#endif + return false; + } + + // We have the same number of pointers as last time. + // A 'jumpy' point is one where the coordinate value for one axis + // has jumped to the other pointer's location. No need to do anything + // else if we only have one pointer. + if (pointerCount < 2) { + return false; + } + + if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) { + int jumpyEpsilon = parameters.yAxis.range / JUMPY_EPSILON_DIVISOR; + + // We only replace the single worst jumpy point as characterized by pointer distance + // in a single axis. + int32_t badPointerIndex = -1; + int32_t badPointerReplacementIndex = -1; + int32_t badPointerDistance = INT_MIN; // distance to be corrected + + for (uint32_t i = pointerCount; i-- > 0; ) { + int32_t x = currentTouch.pointers[i].x; + int32_t y = currentTouch.pointers[i].y; + +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y); +#endif + + // Check if a touch point is too close to another's coordinates + bool dropX = false, dropY = false; + for (uint32_t j = 0; j < pointerCount; j++) { + if (i == j) { + continue; + } + + if (abs(x - currentTouch.pointers[j].x) <= jumpyEpsilon) { + dropX = true; + break; + } + + if (abs(y - currentTouch.pointers[j].y) <= jumpyEpsilon) { + dropY = true; + break; + } + } + if (! dropX && ! dropY) { + continue; // not jumpy + } + + // Find a replacement candidate by comparing with older points on the + // complementary (non-jumpy) axis. + int32_t distance = INT_MIN; // distance to be corrected + int32_t replacementIndex = -1; + + if (dropX) { + // X looks too close. Find an older replacement point with a close Y. + int32_t smallestDeltaY = INT_MAX; + for (uint32_t j = 0; j < pointerCount; j++) { + int32_t deltaY = abs(y - lastTouch.pointers[j].y); + if (deltaY < smallestDeltaY) { + smallestDeltaY = deltaY; + replacementIndex = j; + } + } + distance = abs(x - lastTouch.pointers[replacementIndex].x); + } else { + // Y looks too close. Find an older replacement point with a close X. + int32_t smallestDeltaX = INT_MAX; + for (uint32_t j = 0; j < pointerCount; j++) { + int32_t deltaX = abs(x - lastTouch.pointers[j].x); + if (deltaX < smallestDeltaX) { + smallestDeltaX = deltaX; + replacementIndex = j; + } + } + distance = abs(y - lastTouch.pointers[replacementIndex].y); + } + + // If replacing this pointer would correct a worse error than the previous ones + // considered, then use this replacement instead. + if (distance > badPointerDistance) { + badPointerIndex = i; + badPointerReplacementIndex = replacementIndex; + badPointerDistance = distance; + } + } + + // Correct the jumpy pointer if one was found. + if (badPointerIndex >= 0) { +#if DEBUG_HACKS + LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)", + badPointerIndex, + lastTouch.pointers[badPointerReplacementIndex].x, + lastTouch.pointers[badPointerReplacementIndex].y); +#endif + + currentTouch.pointers[badPointerIndex].x = + lastTouch.pointers[badPointerReplacementIndex].x; + currentTouch.pointers[badPointerIndex].y = + lastTouch.pointers[badPointerReplacementIndex].y; + jumpyTouchFilter.jumpyPointsDropped += 1; + return true; + } + } + + jumpyTouchFilter.jumpyPointsDropped = 0; + return false; +} + +/* Special hack for devices that have bad screen data: aggregate and + * compute averages of the coordinate data, to reduce the amount of + * jitter seen by applications. */ +void InputDevice::TouchScreenState::applyAveragingTouchFilter() { + for (uint32_t currentIndex = 0; currentIndex < currentTouch.pointerCount; currentIndex++) { + uint32_t id = currentTouch.pointers[currentIndex].id; + int32_t x = currentTouch.pointers[currentIndex].x; + int32_t y = currentTouch.pointers[currentIndex].y; + int32_t pressure = currentTouch.pointers[currentIndex].pressure; + + if (lastTouch.idBits.hasBit(id)) { + // Pointer still down compute average. + uint32_t start = averagingTouchFilter.historyStart[id]; + uint32_t end = averagingTouchFilter.historyEnd[id]; + + int64_t deltaX = x - averagingTouchFilter.historyData[end].pointers[id].x; + int64_t deltaY = y - averagingTouchFilter.historyData[end].pointers[id].y; + uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY); + +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld", + id, distance); +#endif + + if (distance < AVERAGING_DISTANCE_LIMIT) { + end += 1; + if (end > AVERAGING_HISTORY_SIZE) { + end = 0; + } + + if (end == start) { + start += 1; + if (start > AVERAGING_HISTORY_SIZE) { + start = 0; + } + } + + 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; + + 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 historicalPressure = averagingTouchFilter.historyData[start] + .pointers[id].pressure; + + averagedX += historicalX; + averagedY += historicalY; + totalPressure += historicalPressure; + + if (start == end) { + break; + } + + start += 1; + if (start > AVERAGING_HISTORY_SIZE) { + start = 0; + } + } + + averagedX /= totalPressure; + averagedY /= totalPressure; + +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - " + "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure, + averagedX, averagedY); +#endif + + currentTouch.pointers[currentIndex].x = averagedX; + currentTouch.pointers[currentIndex].y = averagedY; + } else { +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id); +#endif + } + } else { +#if DEBUG_HACKS + LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id); +#endif + } + + // Reset pointer history. + averagingTouchFilter.historyStart[id] = 0; + averagingTouchFilter.historyEnd[id] = 0; + averagingTouchFilter.historyData[0].pointers[id].x = x; + averagingTouchFilter.historyData[0].pointers[id].y = y; + averagingTouchFilter.historyData[0].pointers[id].pressure = pressure; + } +} + +bool InputDevice::TouchScreenState::isPointInsideDisplay(int32_t x, int32_t y) const { + return x >= parameters.xAxis.minValue + && x <= parameters.xAxis.maxValue + && y >= parameters.yAxis.minValue + && y <= parameters.yAxis.maxValue; +} + + +// --- InputDevice::SingleTouchScreenState --- + +void InputDevice::SingleTouchScreenState::reset() { + accumulator.clear(); + current.down = false; + current.x = 0; + current.y = 0; + current.pressure = 0; + current.size = 0; +} + + +// --- InputDevice::MultiTouchScreenState --- + +void InputDevice::MultiTouchScreenState::reset() { + accumulator.clear(); +} + + +// --- InputReader --- + +InputReader::InputReader(const sp<EventHubInterface>& eventHub, + const sp<InputDispatchPolicyInterface>& policy, + const sp<InputDispatcherInterface>& dispatcher) : + mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher) { + resetGlobalMetaState(); + resetDisplayProperties(); + updateGlobalVirtualKeyState(); +} + +InputReader::~InputReader() { + for (size_t i = 0; i < mDevices.size(); i++) { + delete mDevices.valueAt(i); + } +} + +void InputReader::loopOnce() { + RawEvent rawEvent; + mEventHub->getEvent(& rawEvent.deviceId, & rawEvent.type, & rawEvent.scanCode, + & rawEvent.keyCode, & rawEvent.flags, & rawEvent.value, & rawEvent.when); + + // Replace the event timestamp so it is in same timebase as java.lang.System.nanoTime() + // and android.os.SystemClock.uptimeMillis() as expected by the rest of the system. + rawEvent.when = systemTime(SYSTEM_TIME_MONOTONIC); + +#if DEBUG_RAW_EVENTS + LOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d", + rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode, + rawEvent.value); +#endif + + process(& rawEvent); +} + +void InputReader::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EventHubInterface::DEVICE_ADDED: + handleDeviceAdded(rawEvent); + break; + + case EventHubInterface::DEVICE_REMOVED: + handleDeviceRemoved(rawEvent); + break; + + case EV_SYN: + handleSync(rawEvent); + break; + + case EV_KEY: + handleKey(rawEvent); + break; + + case EV_REL: + handleRelativeMotion(rawEvent); + break; + + case EV_ABS: + handleAbsoluteMotion(rawEvent); + break; + + case EV_SW: + handleSwitch(rawEvent); + break; + } +} + +void InputReader::handleDeviceAdded(const RawEvent* rawEvent) { + InputDevice* device = getDevice(rawEvent->deviceId); + if (device) { + LOGW("Ignoring spurious device added event for deviceId %d.", rawEvent->deviceId); + return; + } + + addDevice(rawEvent->when, rawEvent->deviceId); +} + +void InputReader::handleDeviceRemoved(const RawEvent* rawEvent) { + InputDevice* device = getDevice(rawEvent->deviceId); + if (! device) { + LOGW("Ignoring spurious device removed event for deviceId %d.", rawEvent->deviceId); + return; + } + + removeDevice(rawEvent->when, device); +} + +void InputReader::handleSync(const RawEvent* rawEvent) { + InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); + if (! device) return; + + if (rawEvent->scanCode == SYN_MT_REPORT) { + // MultiTouch Sync: The driver has returned all data for *one* of the pointers. + // We drop pointers with pressure <= 0 since that indicates they are not down. + if (device->isMultiTouchScreen()) { + uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount; + + if (device->multiTouchScreen.accumulator.pointers[pointerIndex].fields) { + if (pointerIndex == MAX_POINTERS) { + LOGW("MultiTouch device driver returned more than maximum of %d pointers.", + MAX_POINTERS); + } else { + pointerIndex += 1; + device->multiTouchScreen.accumulator.pointerCount = pointerIndex; + } + } + + device->multiTouchScreen.accumulator.pointers[pointerIndex].clear(); + } + } else if (rawEvent->scanCode == SYN_REPORT) { + // General Sync: The driver has returned all data for the current event update. + if (device->isMultiTouchScreen()) { + if (device->multiTouchScreen.accumulator.isDirty()) { + onMultiTouchScreenStateChanged(rawEvent->when, device); + device->multiTouchScreen.accumulator.clear(); + } + } else if (device->isSingleTouchScreen()) { + if (device->singleTouchScreen.accumulator.isDirty()) { + onSingleTouchScreenStateChanged(rawEvent->when, device); + device->singleTouchScreen.accumulator.clear(); + } + } + + if (device->trackball.accumulator.isDirty()) { + onTrackballStateChanged(rawEvent->when, device); + device->trackball.accumulator.clear(); + } + } +} + +void InputReader::handleKey(const RawEvent* rawEvent) { + InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); + if (! device) return; + + bool down = rawEvent->value != 0; + int32_t scanCode = rawEvent->scanCode; + + if (device->isKeyboard() && (scanCode < BTN_FIRST || scanCode > BTN_LAST)) { + int32_t keyCode = rawEvent->keyCode; + onKey(rawEvent->when, device, down, keyCode, scanCode, rawEvent->flags); + } else if (device->isSingleTouchScreen()) { + switch (rawEvent->scanCode) { + case BTN_TOUCH: + device->singleTouchScreen.accumulator.fields |= + InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH; + device->singleTouchScreen.accumulator.btnTouch = down; + break; + } + } else if (device->isTrackball()) { + switch (rawEvent->scanCode) { + case BTN_MOUSE: + device->trackball.accumulator.fields |= + InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE; + device->trackball.accumulator.btnMouse = down; + + // send the down immediately + // XXX this emulates the old behavior of KeyInputQueue, unclear whether it is + // necessary or if we can wait until the next sync + onTrackballStateChanged(rawEvent->when, device); + device->trackball.accumulator.clear(); + break; + } + } +} + +void InputReader::handleRelativeMotion(const RawEvent* rawEvent) { + InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); + if (! device) return; + + if (device->isTrackball()) { + switch (rawEvent->scanCode) { + case REL_X: + device->trackball.accumulator.fields |= + InputDevice::TrackballState::Accumulator::FIELD_REL_X; + device->trackball.accumulator.relX = rawEvent->value; + break; + case REL_Y: + device->trackball.accumulator.fields |= + InputDevice::TrackballState::Accumulator::FIELD_REL_Y; + device->trackball.accumulator.relY = rawEvent->value; + break; + } + } +} + +void InputReader::handleAbsoluteMotion(const RawEvent* rawEvent) { + InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); + if (! device) return; + + if (device->isMultiTouchScreen()) { + uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount; + InputDevice::MultiTouchScreenState::Accumulator::Pointer* pointer = + & device->multiTouchScreen.accumulator.pointers[pointerIndex]; + + switch (rawEvent->scanCode) { + case ABS_MT_POSITION_X: + pointer->fields |= + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X; + pointer->absMTPositionX = rawEvent->value; + break; + case ABS_MT_POSITION_Y: + pointer->fields |= + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y; + pointer->absMTPositionY = rawEvent->value; + break; + case ABS_MT_TOUCH_MAJOR: + pointer->fields |= + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR; + pointer->absMTTouchMajor = rawEvent->value; + break; + case ABS_MT_WIDTH_MAJOR: + pointer->fields |= + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR; + pointer->absMTWidthMajor = rawEvent->value; + break; + case ABS_MT_TRACKING_ID: + pointer->fields |= + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TRACKING_ID; + pointer->absMTTrackingId = rawEvent->value; + break; + } + } else if (device->isSingleTouchScreen()) { + switch (rawEvent->scanCode) { + case ABS_X: + device->singleTouchScreen.accumulator.fields |= + InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X; + device->singleTouchScreen.accumulator.absX = rawEvent->value; + break; + case ABS_Y: + device->singleTouchScreen.accumulator.fields |= + InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y; + device->singleTouchScreen.accumulator.absY = rawEvent->value; + break; + case ABS_PRESSURE: + device->singleTouchScreen.accumulator.fields |= + InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE; + device->singleTouchScreen.accumulator.absPressure = rawEvent->value; + break; + case ABS_TOOL_WIDTH: + device->singleTouchScreen.accumulator.fields |= + InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH; + device->singleTouchScreen.accumulator.absToolWidth = rawEvent->value; + break; + } + } +} + +void InputReader::handleSwitch(const RawEvent* rawEvent) { + InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId); + if (! device) return; + + onSwitch(rawEvent->when, device, rawEvent->value != 0, rawEvent->scanCode); +} + +void InputReader::onKey(nsecs_t when, InputDevice* device, + bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) { + /* Refresh display properties so we can rotate key codes according to display orientation */ + + if (! refreshDisplayProperties()) { + return; + } + + /* Update device state */ + + int32_t oldMetaState = device->keyboard.current.metaState; + int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState); + if (oldMetaState != newMetaState) { + device->keyboard.current.metaState = newMetaState; + resetGlobalMetaState(); + } + + // FIXME if we send a down event about a rotated key press we should ensure that we send + // a corresponding up event about the rotated key press even if the orientation + // has changed in the meantime + keyCode = rotateKeyCode(keyCode, mDisplayOrientation); + + if (down) { + device->keyboard.current.downTime = when; + } + + /* Apply policy */ + + int32_t policyActions = mPolicy->interceptKey(when, device->id, + down, keyCode, scanCode, policyFlags); + + if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { + return; // event dropped + } + + /* Enqueue key event for dispatch */ + + int32_t keyEventAction; + if (down) { + device->keyboard.current.downTime = when; + keyEventAction = KEY_EVENT_ACTION_DOWN; + } else { + keyEventAction = KEY_EVENT_ACTION_UP; + } + + int32_t keyEventFlags = KEY_EVENT_FLAG_FROM_SYSTEM; + if (policyActions & InputDispatchPolicyInterface::ACTION_WOKE_HERE) { + keyEventFlags = keyEventFlags | KEY_EVENT_FLAG_WOKE_HERE; + } + + mDispatcher->notifyKey(when, device->id, INPUT_EVENT_NATURE_KEY, policyFlags, + keyEventAction, keyEventFlags, keyCode, scanCode, + device->keyboard.current.metaState, + device->keyboard.current.downTime); +} + +void InputReader::onSwitch(nsecs_t when, InputDevice* device, bool down, + int32_t code) { + switch (code) { + case SW_LID: + mDispatcher->notifyLidSwitchChanged(when, ! down); + } +} + +void InputReader::onMultiTouchScreenStateChanged(nsecs_t when, + InputDevice* device) { + static const uint32_t REQUIRED_FIELDS = + InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X + | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y + | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR + | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR; + + /* Refresh display properties so we can map touch screen coords into display coords */ + + if (! refreshDisplayProperties()) { + return; + } + + /* Update device state */ + + InputDevice::MultiTouchScreenState* in = & device->multiTouchScreen; + InputDevice::TouchData* out = & device->touchScreen.currentTouch; + + uint32_t inCount = in->accumulator.pointerCount; + uint32_t outCount = 0; + bool havePointerIds = true; + + out->clear(); + + for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) { + uint32_t fields = in->accumulator.pointers[inIndex].fields; + + if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) { +#if DEBUG_POINTERS + LOGD("Pointers: Missing required multitouch pointer fields: index=%d, fields=%d", + inIndex, fields); + continue; +#endif + } + + if (in->accumulator.pointers[inIndex].absMTTouchMajor <= 0) { + // Pointer is not down. Drop it. + continue; + } + + // FIXME assignment of pressure may be incorrect, probably better to let + // pressure = touch / width. Later on we pass width to MotionEvent as a size, which + // isn't quite right either. Should be using touch for that. + out->pointers[outCount].x = in->accumulator.pointers[inIndex].absMTPositionX; + out->pointers[outCount].y = in->accumulator.pointers[inIndex].absMTPositionY; + out->pointers[outCount].pressure = in->accumulator.pointers[inIndex].absMTTouchMajor; + out->pointers[outCount].size = in->accumulator.pointers[inIndex].absMTWidthMajor; + + if (havePointerIds) { + if (fields & InputDevice::MultiTouchScreenState::Accumulator:: + FIELD_ABS_MT_TRACKING_ID) { + uint32_t id = uint32_t(in->accumulator.pointers[inIndex].absMTTrackingId); + + if (id > MAX_POINTER_ID) { +#if DEBUG_POINTERS + LOGD("Pointers: Ignoring driver provided pointer id %d because " + "it is larger than max supported id %d for optimizations", + id, MAX_POINTER_ID); +#endif + havePointerIds = false; + } + else { + out->pointers[outCount].id = id; + out->idToIndex[id] = outCount; + out->idBits.markBit(id); + } + } else { + havePointerIds = false; + } + } + + outCount += 1; + } + + out->pointerCount = outCount; + + onTouchScreenChanged(when, device, havePointerIds); +} + +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()) { + return; + } + + /* Update device state */ + + InputDevice::SingleTouchScreenState* in = & device->singleTouchScreen; + InputDevice::TouchData* out = & device->touchScreen.currentTouch; + + uint32_t fields = in->accumulator.fields; + + if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH) { + in->current.down = in->accumulator.btnTouch; + } + + if ((fields & POSITION_FIELDS) == POSITION_FIELDS) { + in->current.x = in->accumulator.absX; + in->current.y = in->accumulator.absY; + in->current.pressure = in->accumulator.absPressure; + in->current.size = in->accumulator.absToolWidth; + } + + out->clear(); + + if (in->current.down) { + out->pointerCount = 1; + out->pointers[0].id = 0; + out->pointers[0].x = in->current.x; + out->pointers[0].y = in->current.y; + out->pointers[0].pressure = in->current.pressure; + out->pointers[0].size = in->current.size; + out->idToIndex[0] = 0; + out->idBits.markBit(0); + } + + onTouchScreenChanged(when, device, true); +} + +void InputReader::onTouchScreenChanged(nsecs_t when, + InputDevice* device, bool havePointerIds) { + /* Apply policy */ + + int32_t policyActions = mPolicy->interceptTouch(when); + + uint32_t policyFlags = 0; + if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { + device->touchScreen.lastTouch.clear(); + return; // event dropped + } + + /* Preprocess pointer data */ + + if (device->touchScreen.parameters.useBadTouchFilter) { + if (device->touchScreen.applyBadTouchFilter()) { + havePointerIds = false; + } + } + + if (device->touchScreen.parameters.useJumpyTouchFilter) { + if (device->touchScreen.applyJumpyTouchFilter()) { + havePointerIds = false; + } + } + + if (! havePointerIds) { + device->touchScreen.calculatePointerIds(); + } + + InputDevice::TouchData temp; + InputDevice::TouchData* savedTouch; + if (device->touchScreen.parameters.useAveragingTouchFilter) { + temp.copyFrom(device->touchScreen.currentTouch); + savedTouch = & temp; + + device->touchScreen.applyAveragingTouchFilter(); + } else { + savedTouch = & device->touchScreen.currentTouch; + } + + /* Process virtual keys or touches */ + + if (! consumeVirtualKeyTouches(when, device, policyFlags)) { + dispatchTouches(when, device, policyFlags); + } + + // Copy current touch to last touch in preparation for the next cycle. + device->touchScreen.lastTouch.copyFrom(*savedTouch); +} + +bool InputReader::consumeVirtualKeyTouches(nsecs_t when, + InputDevice* device, uint32_t policyFlags) { + if (device->touchScreen.currentVirtualKey.down) { + if (device->touchScreen.currentTouch.pointerCount == 0) { + // Pointer went up while virtual key was down. Send key up event. + device->touchScreen.currentVirtualKey.down = false; + +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d", + device->touchScreen.currentVirtualKey.keyCode, + device->touchScreen.currentVirtualKey.scanCode); +#endif + + dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_UP, + KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY); + return true; // consumed + } + + int32_t x = device->touchScreen.currentTouch.pointers[0].x; + int32_t y = device->touchScreen.currentTouch.pointers[0].y; + if (device->touchScreen.isPointInsideDisplay(x, y)) { + // Pointer moved inside the display area. Send key cancellation. + device->touchScreen.currentVirtualKey.down = false; + +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d", + device->touchScreen.currentVirtualKey.keyCode, + device->touchScreen.currentVirtualKey.scanCode); +#endif + + dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_UP, + KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY + | KEY_EVENT_FLAG_CANCELED); + + // Clear the last touch data so we will consider the pointer as having just been + // pressed down when generating subsequent motion events. + device->touchScreen.lastTouch.clear(); + return false; // not consumed + } + } else if (device->touchScreen.currentTouch.pointerCount > 0 + && device->touchScreen.lastTouch.pointerCount == 0) { + int32_t x = device->touchScreen.currentTouch.pointers[0].x; + int32_t y = device->touchScreen.currentTouch.pointers[0].y; + for (size_t i = 0; i < device->touchScreen.virtualKeys.size(); i++) { + const InputDevice::VirtualKey& virtualKey = device->touchScreen.virtualKeys[i]; + +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, " + "left=%d, top=%d, right=%d, bottom=%d", + x, y, + virtualKey.keyCode, virtualKey.scanCode, + virtualKey.hitLeft, virtualKey.hitTop, + virtualKey.hitRight, virtualKey.hitBottom); +#endif + + if (virtualKey.isHit(x, y)) { + device->touchScreen.currentVirtualKey.down = true; + device->touchScreen.currentVirtualKey.downTime = when; + device->touchScreen.currentVirtualKey.keyCode = virtualKey.keyCode; + device->touchScreen.currentVirtualKey.scanCode = virtualKey.scanCode; + +#if DEBUG_VIRTUAL_KEYS + LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d", + device->touchScreen.currentVirtualKey.keyCode, + device->touchScreen.currentVirtualKey.scanCode); +#endif + + dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_DOWN, + KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY); + return true; // consumed + } + } + } + + return false; // not consumed +} + +void InputReader::dispatchVirtualKey(nsecs_t when, + InputDevice* device, uint32_t policyFlags, + int32_t keyEventAction, int32_t keyEventFlags) { + 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(); + + updateGlobalVirtualKeyState(); + + 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); +} + +void InputReader::dispatchTouches(nsecs_t when, + InputDevice* device, uint32_t policyFlags) { + uint32_t currentPointerCount = device->touchScreen.currentTouch.pointerCount; + uint32_t lastPointerCount = device->touchScreen.lastTouch.pointerCount; + if (currentPointerCount == 0 && lastPointerCount == 0) { + return; // nothing to do! + } + + BitSet32 currentIdBits = device->touchScreen.currentTouch.idBits; + BitSet32 lastIdBits = device->touchScreen.lastTouch.idBits; + + if (currentIdBits == lastIdBits) { + // No pointer id changes so this is a move event. + // The dispatcher takes care of batching moves so we don't have to deal with that here. + int32_t motionEventAction = MOTION_EVENT_ACTION_MOVE; + dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch, + currentIdBits, motionEventAction); + } else { + // There may be pointers going up and pointers going down at the same time when pointer + // ids are reported by the device driver. + BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value); + BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value); + BitSet32 activeIdBits(lastIdBits.value); + + while (! upIdBits.isEmpty()) { + uint32_t upId = upIdBits.firstMarkedBit(); + upIdBits.clearBit(upId); + BitSet32 oldActiveIdBits = activeIdBits; + activeIdBits.clearBit(upId); + + int32_t motionEventAction; + if (activeIdBits.isEmpty()) { + motionEventAction = MOTION_EVENT_ACTION_UP; + } else { + motionEventAction = MOTION_EVENT_ACTION_POINTER_UP + | (upId << MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } + + dispatchTouch(when, device, policyFlags, & device->touchScreen.lastTouch, + oldActiveIdBits, motionEventAction); + } + + while (! downIdBits.isEmpty()) { + uint32_t downId = downIdBits.firstMarkedBit(); + downIdBits.clearBit(downId); + BitSet32 oldActiveIdBits = activeIdBits; + activeIdBits.markBit(downId); + + int32_t motionEventAction; + if (oldActiveIdBits.isEmpty()) { + motionEventAction = MOTION_EVENT_ACTION_DOWN; + device->touchScreen.downTime = when; + } else { + motionEventAction = MOTION_EVENT_ACTION_POINTER_DOWN + | (downId << MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } + + dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch, + activeIdBits, motionEventAction); + } + } +} + +void InputReader::dispatchTouch(nsecs_t when, InputDevice* device, uint32_t policyFlags, + InputDevice::TouchData* touch, BitSet32 idBits, + int32_t motionEventAction) { + int32_t orientedWidth, orientedHeight; + switch (mDisplayOrientation) { + case InputDispatchPolicyInterface::ROTATION_90: + case InputDispatchPolicyInterface::ROTATION_270: + orientedWidth = mDisplayHeight; + orientedHeight = mDisplayWidth; + break; + default: + orientedWidth = mDisplayWidth; + orientedHeight = mDisplayHeight; + break; + } + + uint32_t pointerCount = 0; + int32_t pointerIds[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + + // Walk through the the active pointers and map touch screen coordinates (TouchData) into + // display coordinates (PointerCoords) and adjust for display orientation. + while (! idBits.isEmpty()) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = touch->idToIndex[id]; + + float x = (float(touch->pointers[index].x) + - device->touchScreen.parameters.xAxis.minValue) + * device->touchScreen.precalculated.xScale; + float y = (float(touch->pointers[index].y) + - device->touchScreen.parameters.yAxis.minValue) + * device->touchScreen.precalculated.yScale; + float pressure = (float(touch->pointers[index].pressure) + - device->touchScreen.parameters.pressureAxis.minValue) + * device->touchScreen.precalculated.pressureScale; + float size = (float(touch->pointers[index].size) + - device->touchScreen.parameters.sizeAxis.minValue) + * device->touchScreen.precalculated.sizeScale; + + switch (mDisplayOrientation) { + case InputDispatchPolicyInterface::ROTATION_90: { + float xTemp = x; + x = y; + y = mDisplayHeight - xTemp; + break; + } + case InputDispatchPolicyInterface::ROTATION_180: { + x = mDisplayWidth - x; + y = mDisplayHeight - y; + break; + } + case InputDispatchPolicyInterface::ROTATION_270: { + float xTemp = x; + x = mDisplayWidth - y; + y = xTemp; + break; + } + } + + pointerIds[pointerCount] = int32_t(id); + + pointerCoords[pointerCount].x = x; + pointerCoords[pointerCount].y = y; + pointerCoords[pointerCount].pressure = pressure; + pointerCoords[pointerCount].size = size; + + pointerCount += 1; + } + + // Check edge flags by looking only at the first pointer since the flags are + // global to the event. + // XXX Maybe we should revise the edge flags API to work on a per-pointer basis. + int32_t motionEventEdgeFlags = 0; + if (motionEventAction == MOTION_EVENT_ACTION_DOWN) { + if (pointerCoords[0].x <= 0) { + motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_LEFT; + } else if (pointerCoords[0].x >= orientedWidth) { + motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_RIGHT; + } + if (pointerCoords[0].y <= 0) { + motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_TOP; + } else if (pointerCoords[0].y >= orientedHeight) { + motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_BOTTOM; + } + } + + nsecs_t downTime = device->touchScreen.downTime; + mDispatcher->notifyMotion(when, device->id, INPUT_EVENT_NATURE_TOUCH, policyFlags, + motionEventAction, globalMetaState(), motionEventEdgeFlags, + pointerCount, pointerIds, pointerCoords, + 0, 0, downTime); +} + +void InputReader::onTrackballStateChanged(nsecs_t when, + InputDevice* device) { + static const uint32_t DELTA_FIELDS = + InputDevice::TrackballState::Accumulator::FIELD_REL_X + | InputDevice::TrackballState::Accumulator::FIELD_REL_Y; + + /* Refresh display properties so we can trackball moves according to display orientation */ + + if (! refreshDisplayProperties()) { + return; + } + + /* Update device state */ + + uint32_t fields = device->trackball.accumulator.fields; + bool downChanged = fields & InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE; + bool deltaChanged = (fields & DELTA_FIELDS) == DELTA_FIELDS; + + bool down; + if (downChanged) { + if (device->trackball.accumulator.btnMouse) { + device->trackball.current.down = true; + device->trackball.current.downTime = when; + down = true; + } else { + device->trackball.current.down = false; + down = false; + } + } else { + down = device->trackball.current.down; + } + + /* Apply policy */ + + int32_t policyActions = mPolicy->interceptTrackball(when, downChanged, down, deltaChanged); + + uint32_t policyFlags = 0; + if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) { + return; // event dropped + } + + /* Enqueue motion event for dispatch */ + + int32_t motionEventAction; + if (downChanged) { + motionEventAction = down ? MOTION_EVENT_ACTION_DOWN : MOTION_EVENT_ACTION_UP; + } else { + motionEventAction = MOTION_EVENT_ACTION_MOVE; + } + + int32_t pointerId = 0; + PointerCoords pointerCoords; + pointerCoords.x = device->trackball.accumulator.relX + * device->trackball.precalculated.xScale; + pointerCoords.y = device->trackball.accumulator.relY + * device->trackball.precalculated.yScale; + pointerCoords.pressure = 1.0f; // XXX Consider making this 1.0f if down, 0 otherwise. + pointerCoords.size = 0; + + float temp; + switch (mDisplayOrientation) { + case InputDispatchPolicyInterface::ROTATION_90: + temp = pointerCoords.x; + pointerCoords.x = pointerCoords.y; + pointerCoords.y = - temp; + break; + + case InputDispatchPolicyInterface::ROTATION_180: + pointerCoords.x = - pointerCoords.x; + pointerCoords.y = - pointerCoords.y; + break; + + case InputDispatchPolicyInterface::ROTATION_270: + temp = pointerCoords.x; + pointerCoords.x = - pointerCoords.y; + pointerCoords.y = temp; + break; + } + + mDispatcher->notifyMotion(when, device->id, INPUT_EVENT_NATURE_TRACKBALL, policyFlags, + motionEventAction, globalMetaState(), MOTION_EVENT_EDGE_FLAG_NONE, + 1, & pointerId, & pointerCoords, + device->trackball.precalculated.xPrecision, + device->trackball.precalculated.yPrecision, + device->trackball.current.downTime); +} + +void InputReader::onConfigurationChanged(nsecs_t when) { + // Reset global meta state because it depends on the list of all configured devices. + resetGlobalMetaState(); + + // Reset virtual keys, just in case. + updateGlobalVirtualKeyState(); + + // Enqueue configuration changed. + // XXX This stuff probably needs to be tracked elsewhere in an input device registry + // of some kind that can be asynchronously updated and queried. (Same as above?) + int32_t touchScreenConfig = InputDispatchPolicyInterface::TOUCHSCREEN_NOTOUCH; + int32_t keyboardConfig = InputDispatchPolicyInterface::KEYBOARD_NOKEYS; + int32_t navigationConfig = InputDispatchPolicyInterface::NAVIGATION_NONAV; + + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + int32_t deviceClasses = device->classes; + + if (deviceClasses & INPUT_DEVICE_CLASS_TOUCHSCREEN) { + touchScreenConfig = InputDispatchPolicyInterface::TOUCHSCREEN_FINGER; + } + if (deviceClasses & INPUT_DEVICE_CLASS_ALPHAKEY) { + keyboardConfig = InputDispatchPolicyInterface::KEYBOARD_QWERTY; + } + if (deviceClasses & INPUT_DEVICE_CLASS_TRACKBALL) { + navigationConfig = InputDispatchPolicyInterface::NAVIGATION_TRACKBALL; + } else if (deviceClasses & INPUT_DEVICE_CLASS_DPAD) { + navigationConfig = InputDispatchPolicyInterface::NAVIGATION_DPAD; + } + } + + mDispatcher->notifyConfigurationChanged(when, touchScreenConfig, + keyboardConfig, navigationConfig); +} + +bool InputReader::applyStandardInputDispatchPolicyActions(nsecs_t when, + int32_t policyActions, uint32_t* policyFlags) { + if (policyActions & InputDispatchPolicyInterface::ACTION_APP_SWITCH_COMING) { + mDispatcher->notifyAppSwitchComing(when); + } + + if (policyActions & InputDispatchPolicyInterface::ACTION_WOKE_HERE) { + *policyFlags |= POLICY_FLAG_WOKE_HERE; + } + + if (policyActions & InputDispatchPolicyInterface::ACTION_BRIGHT_HERE) { + *policyFlags |= POLICY_FLAG_BRIGHT_HERE; + } + + return policyActions & InputDispatchPolicyInterface::ACTION_DISPATCH; +} + +void InputReader::resetDisplayProperties() { + mDisplayWidth = mDisplayHeight = -1; + mDisplayOrientation = -1; +} + +bool InputReader::refreshDisplayProperties() { + int32_t newWidth, newHeight, newOrientation; + if (mPolicy->getDisplayInfo(0, & newWidth, & newHeight, & newOrientation)) { + if (newWidth != mDisplayWidth || newHeight != mDisplayHeight) { + LOGD("Display size changed from %dx%d to %dx%d, updating device configuration", + mDisplayWidth, mDisplayHeight, newWidth, newHeight); + + mDisplayWidth = newWidth; + mDisplayHeight = newHeight; + + for (size_t i = 0; i < mDevices.size(); i++) { + configureDeviceForCurrentDisplaySize(mDevices.valueAt(i)); + } + } + + mDisplayOrientation = newOrientation; + return true; + } else { + resetDisplayProperties(); + return false; + } +} + +InputDevice* InputReader::getDevice(int32_t deviceId) { + ssize_t index = mDevices.indexOfKey(deviceId); + return index >= 0 ? mDevices.valueAt((size_t) index) : NULL; +} + +InputDevice* InputReader::getNonIgnoredDevice(int32_t deviceId) { + InputDevice* device = getDevice(deviceId); + return device && ! device->ignored ? device : NULL; +} + +void InputReader::addDevice(nsecs_t when, int32_t deviceId) { + uint32_t classes = mEventHub->getDeviceClasses(deviceId); + String8 name = mEventHub->getDeviceName(deviceId); + InputDevice* device = new InputDevice(deviceId, classes, name); + + if (classes != 0) { + LOGI("Device added: id=0x%x, name=%s, classes=%02x", device->id, + device->name.string(), device->classes); + + configureDevice(device); + } else { + LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", device->id, + device->name.string()); + + device->ignored = true; + } + + device->reset(); + + mDevices.add(deviceId, device); + + if (! device->ignored) { + onConfigurationChanged(when); + } +} + +void InputReader::removeDevice(nsecs_t when, InputDevice* device) { + mDevices.removeItem(device->id); + + if (! device->ignored) { + LOGI("Device removed: id=0x%x, name=%s, classes=%02x", device->id, + device->name.string(), device->classes); + + onConfigurationChanged(when); + } else { + LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)", device->id, + device->name.string()); + } + + delete device; +} + +void InputReader::configureDevice(InputDevice* device) { + if (device->isMultiTouchScreen()) { + configureAbsoluteAxisInfo(device, ABS_MT_POSITION_X, "X", + & device->touchScreen.parameters.xAxis); + configureAbsoluteAxisInfo(device, ABS_MT_POSITION_Y, "Y", + & device->touchScreen.parameters.yAxis); + configureAbsoluteAxisInfo(device, ABS_MT_TOUCH_MAJOR, "Pressure", + & device->touchScreen.parameters.pressureAxis); + configureAbsoluteAxisInfo(device, ABS_MT_WIDTH_MAJOR, "Size", + & device->touchScreen.parameters.sizeAxis); + } else if (device->isSingleTouchScreen()) { + configureAbsoluteAxisInfo(device, ABS_X, "X", + & device->touchScreen.parameters.xAxis); + configureAbsoluteAxisInfo(device, ABS_Y, "Y", + & device->touchScreen.parameters.yAxis); + configureAbsoluteAxisInfo(device, ABS_PRESSURE, "Pressure", + & device->touchScreen.parameters.pressureAxis); + configureAbsoluteAxisInfo(device, ABS_TOOL_WIDTH, "Size", + & device->touchScreen.parameters.sizeAxis); + } + + if (device->isTouchScreen()) { + device->touchScreen.parameters.useBadTouchFilter = + mPolicy->filterTouchEvents(); + device->touchScreen.parameters.useAveragingTouchFilter = + mPolicy->filterTouchEvents(); + device->touchScreen.parameters.useJumpyTouchFilter = + mPolicy->filterJumpyTouchEvents(); + + device->touchScreen.precalculated.pressureScale = + 1.0f / device->touchScreen.parameters.pressureAxis.range; + device->touchScreen.precalculated.sizeScale = + 1.0f / device->touchScreen.parameters.sizeAxis.range; + } + + if (device->isTrackball()) { + device->trackball.precalculated.xPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + device->trackball.precalculated.yPrecision = TRACKBALL_MOVEMENT_THRESHOLD; + device->trackball.precalculated.xScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + device->trackball.precalculated.yScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD; + } + + configureDeviceForCurrentDisplaySize(device); +} + +void InputReader::configureDeviceForCurrentDisplaySize(InputDevice* device) { + if (device->isTouchScreen()) { + if (mDisplayWidth < 0) { + LOGD("Skipping part of touch screen configuration since display size is unknown."); + } else { + LOGI("Device configured: id=0x%x, name=%s (display size was changed)", device->id, + device->name.string()); + configureVirtualKeys(device); + + device->touchScreen.precalculated.xScale = + float(mDisplayWidth) / device->touchScreen.parameters.xAxis.range; + device->touchScreen.precalculated.yScale = + float(mDisplayHeight) / device->touchScreen.parameters.yAxis.range; + } + } +} + +void InputReader::configureVirtualKeys(InputDevice* device) { + device->touchScreen.virtualKeys.clear(); + + Vector<InputDispatchPolicyInterface::VirtualKeyDefinition> virtualKeyDefinitions; + mPolicy->getVirtualKeyDefinitions(device->name, virtualKeyDefinitions); + if (virtualKeyDefinitions.size() == 0) { + return; + } + + device->touchScreen.virtualKeys.setCapacity(virtualKeyDefinitions.size()); + + int32_t touchScreenLeft = device->touchScreen.parameters.xAxis.minValue; + int32_t touchScreenTop = device->touchScreen.parameters.yAxis.minValue; + int32_t touchScreenWidth = device->touchScreen.parameters.xAxis.range; + int32_t touchScreenHeight = device->touchScreen.parameters.yAxis.range; + + for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) { + const InputDispatchPolicyInterface::VirtualKeyDefinition& virtualKeyDefinition = + virtualKeyDefinitions[i]; + + device->touchScreen.virtualKeys.add(); + InputDevice::VirtualKey& virtualKey = + device->touchScreen.virtualKeys.editTop(); + + virtualKey.scanCode = virtualKeyDefinition.scanCode; + int32_t keyCode; + uint32_t flags; + if (mEventHub->scancodeToKeycode(device->id, virtualKey.scanCode, + & keyCode, & flags)) { + LOGI(" VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode); + device->touchScreen.virtualKeys.pop(); // drop the key + continue; + } + + virtualKey.keyCode = keyCode; + virtualKey.flags = flags; + + // convert the key definition's display coordinates into touch coordinates for a hit box + int32_t halfWidth = virtualKeyDefinition.width / 2; + int32_t halfHeight = virtualKeyDefinition.height / 2; + + virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth) + * touchScreenWidth / mDisplayWidth + touchScreenLeft; + virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth) + * touchScreenWidth / mDisplayWidth + touchScreenLeft; + virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight) + * touchScreenHeight / mDisplayHeight + touchScreenTop; + virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight) + * touchScreenHeight / mDisplayHeight + touchScreenTop; + + LOGI(" VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d", + virtualKey.scanCode, virtualKey.keyCode, + virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom); + } +} + +void InputReader::configureAbsoluteAxisInfo(InputDevice* device, + int axis, const char* name, InputDevice::AbsoluteAxisInfo* out) { + if (! mEventHub->getAbsoluteInfo(device->id, axis, + & out->minValue, & out->maxValue, & out->flat, &out->fuzz)) { + out->range = out->maxValue - out->minValue; + if (out->range != 0) { + LOGI(" %s: min=%d max=%d flat=%d fuzz=%d", + name, out->minValue, out->maxValue, out->flat, out->fuzz); + return; + } + } + + out->minValue = 0; + out->maxValue = 0; + out->flat = 0; + out->fuzz = 0; + out->range = 0; + LOGI(" %s: unknown axis values, setting to zero", name); +} + +void InputReader::resetGlobalMetaState() { + mGlobalMetaState = -1; +} + +int32_t InputReader::globalMetaState() { + if (mGlobalMetaState == -1) { + mGlobalMetaState = 0; + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + if (device->isKeyboard()) { + mGlobalMetaState |= device->keyboard.current.metaState; + } + } + } + return mGlobalMetaState; +} + +void InputReader::updateGlobalVirtualKeyState() { + int32_t keyCode = -1, scanCode = -1; + + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + if (device->isTouchScreen()) { + if (device->touchScreen.currentVirtualKey.down) { + keyCode = device->touchScreen.currentVirtualKey.keyCode; + scanCode = device->touchScreen.currentVirtualKey.scanCode; + } + } + } + + { + AutoMutex _l(mExportedStateLock); + + mGlobalVirtualKeyCode = keyCode; + mGlobalVirtualScanCode = scanCode; + } +} + +bool InputReader::getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const { + AutoMutex _l(mExportedStateLock); + + *outKeyCode = mGlobalVirtualKeyCode; + *outScanCode = mGlobalVirtualScanCode; + return mGlobalVirtualKeyCode != -1; +} + + +// --- InputReaderThread --- + +InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) : + Thread(/*canCallJava*/ true), mReader(reader) { +} + +InputReaderThread::~InputReaderThread() { +} + +bool InputReaderThread::threadLoop() { + mReader->loopOnce(); + return true; +} + +} // namespace android diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp new file mode 100644 index 0000000..a24180f --- /dev/null +++ b/libs/ui/InputTransport.cpp @@ -0,0 +1,684 @@ +// +// Copyright 2010 The Android Open Source Project +// +// Provides a shared memory transport for input events. +// +#define LOG_TAG "InputTransport" + +//#define LOG_NDEBUG 0 + +// Log debug messages about channel signalling (send signal, receive signal) +#define DEBUG_CHANNEL_SIGNALS 1 + +// Log debug messages whenever InputChannel objects are created/destroyed +#define DEBUG_CHANNEL_LIFECYCLE 1 + +// Log debug messages about transport actions (initialize, reset, publish, ...) +#define DEBUG_TRANSPORT_ACTIONS 1 + + +#include <cutils/ashmem.h> +#include <cutils/log.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <ui/InputTransport.h> +#include <unistd.h> + +namespace android { + +// Must be at least sizeof(InputMessage) + sufficient space for pointer data +static const int DEFAULT_MESSAGE_BUFFER_SIZE = 16384; + +// Signal sent by the producer to the consumer to inform it that a new message is +// available to be consumed in the shared memory buffer. +static const char INPUT_SIGNAL_DISPATCH = 'D'; + +// Signal sent by the consumer to the producer to inform it that it has finished +// consuming the most recent message. +static const char INPUT_SIGNAL_FINISHED = 'f'; + + +// --- InputChannel --- + +InputChannel::InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd, + int32_t sendPipeFd) : + mName(name), mAshmemFd(ashmemFd), mReceivePipeFd(receivePipeFd), mSendPipeFd(sendPipeFd) { +#if DEBUG_CHANNEL_LIFECYCLE + LOGD("Input channel constructed: name='%s', ashmemFd=%d, receivePipeFd=%d, sendPipeFd=%d", + mName.string(), ashmemFd, receivePipeFd, sendPipeFd); +#endif + + int result = fcntl(mReceivePipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make receive pipe " + "non-blocking. errno=%d", mName.string(), errno); + + result = fcntl(mSendPipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make send pipe " + "non-blocking. errno=%d", mName.string(), errno); +} + +InputChannel::~InputChannel() { +#if DEBUG_CHANNEL_LIFECYCLE + LOGD("Input channel destroyed: name='%s', ashmemFd=%d, receivePipeFd=%d, sendPipeFd=%d", + mName.string(), mAshmemFd, mReceivePipeFd, mSendPipeFd); +#endif + + ::close(mAshmemFd); + ::close(mReceivePipeFd); + ::close(mSendPipeFd); +} + +status_t InputChannel::openInputChannelPair(const String8& name, + InputChannel** outServerChannel, InputChannel** outClientChannel) { + status_t result; + + int serverAshmemFd = ashmem_create_region(name.string(), DEFAULT_MESSAGE_BUFFER_SIZE); + if (serverAshmemFd < 0) { + result = -errno; + LOGE("channel '%s' ~ Could not create shared memory region. errno=%d", + name.string(), errno); + } else { + result = ashmem_set_prot_region(serverAshmemFd, PROT_READ | PROT_WRITE); + if (result < 0) { + LOGE("channel '%s' ~ Error %d trying to set protection of ashmem fd %d.", + name.string(), result, serverAshmemFd); + } else { + // Dup the file descriptor because the server and client input channel objects that + // are returned may have different lifetimes but they share the same shared memory region. + int clientAshmemFd; + clientAshmemFd = dup(serverAshmemFd); + if (clientAshmemFd < 0) { + result = -errno; + LOGE("channel '%s' ~ Could not dup() shared memory region fd. errno=%d", + name.string(), errno); + } else { + int forward[2]; + if (pipe(forward)) { + result = -errno; + LOGE("channel '%s' ~ Could not create forward pipe. errno=%d", + name.string(), errno); + } else { + int reverse[2]; + if (pipe(reverse)) { + result = -errno; + LOGE("channel '%s' ~ Could not create reverse pipe. errno=%d", + name.string(), errno); + } else { + String8 serverChannelName = name; + serverChannelName.append(" (server)"); + *outServerChannel = new InputChannel(serverChannelName, + serverAshmemFd, reverse[0], forward[1]); + + String8 clientChannelName = name; + clientChannelName.append(" (client)"); + *outClientChannel = new InputChannel(clientChannelName, + clientAshmemFd, forward[0], reverse[1]); + return OK; + } + ::close(forward[0]); + ::close(forward[1]); + } + ::close(clientAshmemFd); + } + } + ::close(serverAshmemFd); + } + + *outServerChannel = NULL; + *outClientChannel = NULL; + return result; +} + +status_t InputChannel::sendSignal(char signal) { + ssize_t nWrite = ::write(mSendPipeFd, & signal, 1); + + if (nWrite == 1) { +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ sent signal '%c'", mName.string(), signal); +#endif + return OK; + } + +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ error sending signal '%c', errno=%d", mName.string(), signal, errno); +#endif + return -errno; +} + +status_t InputChannel::receiveSignal(char* outSignal) { + ssize_t nRead = ::read(mReceivePipeFd, outSignal, 1); + if (nRead == 1) { +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ received signal '%c'", mName.string(), *outSignal); +#endif + return OK; + } + + if (errno == EAGAIN) { +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ receive signal failed because no signal available", mName.string()); +#endif + return WOULD_BLOCK; + } + +#if DEBUG_CHANNEL_SIGNALS + LOGD("channel '%s' ~ receive signal failed, errno=%d", mName.string(), errno); +#endif + return -errno; +} + + +// --- InputPublisher --- + +InputPublisher::InputPublisher(const sp<InputChannel>& channel) : + mChannel(channel), mSharedMessage(NULL), + mPinned(false), mSemaphoreInitialized(false), mWasDispatched(false), + mMotionEventSampleDataTail(NULL) { +} + +InputPublisher::~InputPublisher() { + reset(); + + if (mSharedMessage) { + munmap(mSharedMessage, mAshmemSize); + } +} + +status_t InputPublisher::initialize() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ initialize", + mChannel->getName().string()); +#endif + + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_get_size_region(ashmemFd); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d getting size of ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + mAshmemSize = (size_t) result; + + mSharedMessage = static_cast<InputMessage*>(mmap(NULL, mAshmemSize, + PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0)); + if (! mSharedMessage) { + LOGE("channel '%s' publisher ~ mmap failed on ashmem fd %d.", + mChannel->getName().string(), ashmemFd); + return NO_MEMORY; + } + + mPinned = true; + mSharedMessage->consumed = false; + + return reset(); +} + +status_t InputPublisher::reset() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ reset", + mChannel->getName().string()); +#endif + + if (mPinned) { + // Destroy the semaphore since we are about to unpin the memory region that contains it. + int result; + if (mSemaphoreInitialized) { + if (mSharedMessage->consumed) { + result = sem_post(& mSharedMessage->semaphore); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d in sem_post.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + } + + result = sem_destroy(& mSharedMessage->semaphore); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d in sem_destroy.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + + mSemaphoreInitialized = false; + } + + // Unpin the region since we no longer care about its contents. + int ashmemFd = mChannel->getAshmemFd(); + result = ashmem_unpin_region(ashmemFd, 0, 0); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d unpinning ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + mPinned = false; + } + + mMotionEventSampleDataTail = NULL; + mWasDispatched = false; + return OK; +} + +status_t InputPublisher::publishInputEvent( + int32_t type, + int32_t deviceId, + int32_t nature) { + if (mPinned) { + LOGE("channel '%s' publisher ~ Attempted to publish a new event but publisher has " + "not yet been reset.", mChannel->getName().string()); + return INVALID_OPERATION; + } + + // Pin the region. + // We do not check for ASHMEM_NOT_PURGED because we don't care about the previous + // contents of the buffer so it does not matter whether it was purged in the meantime. + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_pin_region(ashmemFd, 0, 0); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d pinning ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + mPinned = true; + + result = sem_init(& mSharedMessage->semaphore, 1, 1); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d in sem_init.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + + mSemaphoreInitialized = true; + + mSharedMessage->consumed = false; + mSharedMessage->type = type; + mSharedMessage->deviceId = deviceId; + mSharedMessage->nature = nature; + return OK; +} + +status_t InputPublisher::publishKeyEvent( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t flags, + int32_t keyCode, + int32_t scanCode, + int32_t metaState, + int32_t repeatCount, + nsecs_t downTime, + nsecs_t eventTime) { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ publishKeyEvent: deviceId=%d, nature=%d, " + "action=%d, flags=%d, keyCode=%d, scanCode=%d, metaState=%d, repeatCount=%d," + "downTime=%lld, eventTime=%lld", + mChannel->getName().string(), + deviceId, nature, action, flags, keyCode, scanCode, metaState, repeatCount, + downTime, eventTime); +#endif + + status_t result = publishInputEvent(INPUT_EVENT_TYPE_KEY, deviceId, nature); + if (result < 0) { + return result; + } + + mSharedMessage->key.action = action; + mSharedMessage->key.flags = flags; + mSharedMessage->key.keyCode = keyCode; + mSharedMessage->key.scanCode = scanCode; + mSharedMessage->key.metaState = metaState; + mSharedMessage->key.repeatCount = repeatCount; + mSharedMessage->key.downTime = downTime; + mSharedMessage->key.eventTime = eventTime; + return OK; +} + +status_t InputPublisher::publishMotionEvent( + int32_t deviceId, + int32_t nature, + int32_t action, + int32_t edgeFlags, + int32_t metaState, + float xOffset, + float yOffset, + float xPrecision, + float yPrecision, + nsecs_t downTime, + nsecs_t eventTime, + size_t pointerCount, + const int32_t* pointerIds, + const PointerCoords* pointerCoords) { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ publishMotionEvent: deviceId=%d, nature=%d, " + "action=%d, edgeFlags=%d, metaState=%d, xOffset=%f, yOffset=%f, " + "xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, " + "pointerCount=%d", + mChannel->getName().string(), + deviceId, nature, action, edgeFlags, metaState, xOffset, yOffset, + xPrecision, yPrecision, downTime, eventTime, pointerCount); +#endif + + if (pointerCount > MAX_POINTERS || pointerCount < 1) { + LOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.", + mChannel->getName().string(), pointerCount); + return BAD_VALUE; + } + + status_t result = publishInputEvent(INPUT_EVENT_TYPE_MOTION, deviceId, nature); + if (result < 0) { + return result; + } + + mSharedMessage->motion.action = action; + mSharedMessage->motion.edgeFlags = edgeFlags; + mSharedMessage->motion.metaState = metaState; + mSharedMessage->motion.xOffset = xOffset; + mSharedMessage->motion.yOffset = yOffset; + mSharedMessage->motion.xPrecision = xPrecision; + mSharedMessage->motion.yPrecision = yPrecision; + mSharedMessage->motion.downTime = downTime; + mSharedMessage->motion.pointerCount = pointerCount; + + mSharedMessage->motion.sampleCount = 1; + mSharedMessage->motion.sampleData[0].eventTime = eventTime; + + for (size_t i = 0; i < pointerCount; i++) { + mSharedMessage->motion.pointerIds[i] = pointerIds[i]; + mSharedMessage->motion.sampleData[0].coords[i] = pointerCoords[i]; + } + + // Cache essential information about the motion event to ensure that a malicious consumer + // cannot confuse the publisher by modifying the contents of the shared memory buffer while + // it is being updated. + if (action == MOTION_EVENT_ACTION_MOVE) { + mMotionEventPointerCount = pointerCount; + mMotionEventSampleDataStride = InputMessage::sampleDataStride(pointerCount); + mMotionEventSampleDataTail = InputMessage::sampleDataPtrIncrement( + mSharedMessage->motion.sampleData, mMotionEventSampleDataStride); + } else { + mMotionEventSampleDataTail = NULL; + } + return OK; +} + +status_t InputPublisher::appendMotionSample( + nsecs_t eventTime, + const PointerCoords* pointerCoords) { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ appendMotionSample: eventTime=%lld", + mChannel->getName().string(), eventTime); +#endif + + if (! mPinned || ! mMotionEventSampleDataTail) { + LOGE("channel '%s' publisher ~ Cannot append motion sample because there is no current " + "MOTION_EVENT_ACTION_MOVE event.", mChannel->getName().string()); + return INVALID_OPERATION; + } + + InputMessage::SampleData* newTail = InputMessage::sampleDataPtrIncrement( + mMotionEventSampleDataTail, mMotionEventSampleDataStride); + size_t newBytesUsed = reinterpret_cast<char*>(newTail) - + reinterpret_cast<char*>(mSharedMessage); + + if (newBytesUsed > mAshmemSize) { + 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); + return NO_MEMORY; + } + + int result; + if (mWasDispatched) { + result = sem_trywait(& mSharedMessage->semaphore); + if (result < 0) { + 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. + LOGD("channel '%s' publisher ~ Cannot append motion sample because the message has " + "already been consumed.", mChannel->getName().string()); + return FAILED_TRANSACTION; + } else { + LOGE("channel '%s' publisher ~ Error %d in sem_trywait.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + } + } + + mMotionEventSampleDataTail->eventTime = eventTime; + for (size_t i = 0; i < mMotionEventPointerCount; i++) { + mMotionEventSampleDataTail->coords[i] = pointerCoords[i]; + } + mMotionEventSampleDataTail = newTail; + + mSharedMessage->motion.sampleCount += 1; + + if (mWasDispatched) { + result = sem_post(& mSharedMessage->semaphore); + if (result < 0) { + LOGE("channel '%s' publisher ~ Error %d in sem_post.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + } + return OK; +} + +status_t InputPublisher::sendDispatchSignal() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ sendDispatchSignal", + mChannel->getName().string()); +#endif + + mWasDispatched = true; + return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH); +} + +status_t InputPublisher::receiveFinishedSignal() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' publisher ~ receiveFinishedSignal", + mChannel->getName().string()); +#endif + + char signal; + status_t result = mChannel->receiveSignal(& signal); + if (result) { + return result; + } + if (signal != INPUT_SIGNAL_FINISHED) { + LOGE("channel '%s' publisher ~ Received unexpected signal '%c' from consumer", + mChannel->getName().string(), signal); + return UNKNOWN_ERROR; + } + return OK; +} + +// --- InputConsumer --- + +InputConsumer::InputConsumer(const sp<InputChannel>& channel) : + mChannel(channel), mSharedMessage(NULL) { +} + +InputConsumer::~InputConsumer() { + if (mSharedMessage) { + munmap(mSharedMessage, mAshmemSize); + } +} + +status_t InputConsumer::initialize() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' consumer ~ initialize", + mChannel->getName().string()); +#endif + + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_get_size_region(ashmemFd); + if (result < 0) { + LOGE("channel '%s' consumer ~ Error %d getting size of ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + mAshmemSize = (size_t) result; + + mSharedMessage = static_cast<InputMessage*>(mmap(NULL, mAshmemSize, + PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0)); + if (! mSharedMessage) { + LOGE("channel '%s' consumer ~ mmap failed on ashmem fd %d.", + mChannel->getName().string(), ashmemFd); + return NO_MEMORY; + } + + return OK; +} + +status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** event) { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' consumer ~ consume", + mChannel->getName().string()); +#endif + + *event = NULL; + + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_pin_region(ashmemFd, 0, 0); + if (result != ASHMEM_NOT_PURGED) { + if (result == ASHMEM_WAS_PURGED) { + LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d because it was purged " + "which probably indicates that the publisher and consumer are out of sync.", + mChannel->getName().string(), result, ashmemFd); + return INVALID_OPERATION; + } + + LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + if (mSharedMessage->consumed) { + LOGE("channel '%s' consumer ~ The current message has already been consumed.", + mChannel->getName().string()); + return INVALID_OPERATION; + } + + // Acquire but *never release* the semaphore. Contention on the semaphore is used to signal + // to the publisher that the message has been consumed (or is in the process of being + // consumed). Eventually the publisher will reinitialize the semaphore for the next message. + result = sem_wait(& mSharedMessage->semaphore); + if (result < 0) { + LOGE("channel '%s' consumer ~ Error %d in sem_wait.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + + mSharedMessage->consumed = true; + + switch (mSharedMessage->type) { + case INPUT_EVENT_TYPE_KEY: { + KeyEvent* keyEvent = factory->createKeyEvent(); + if (! keyEvent) return NO_MEMORY; + + populateKeyEvent(keyEvent); + + *event = keyEvent; + break; + } + + case INPUT_EVENT_TYPE_MOTION: { + MotionEvent* motionEvent = factory->createMotionEvent(); + if (! motionEvent) return NO_MEMORY; + + populateMotionEvent(motionEvent); + + *event = motionEvent; + break; + } + + default: + LOGE("channel '%s' consumer ~ Received message of unknown type %d", + mChannel->getName().string(), mSharedMessage->type); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t InputConsumer::sendFinishedSignal() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' consumer ~ sendFinishedSignal", + mChannel->getName().string()); +#endif + + return mChannel->sendSignal(INPUT_SIGNAL_FINISHED); +} + +status_t InputConsumer::receiveDispatchSignal() { +#if DEBUG_TRANSPORT_ACTIONS + LOGD("channel '%s' consumer ~ receiveDispatchSignal", + mChannel->getName().string()); +#endif + + char signal; + status_t result = mChannel->receiveSignal(& signal); + if (result) { + return result; + } + if (signal != INPUT_SIGNAL_DISPATCH) { + LOGE("channel '%s' consumer ~ Received unexpected signal '%c' from publisher", + mChannel->getName().string(), signal); + return UNKNOWN_ERROR; + } + return OK; +} + +void InputConsumer::populateKeyEvent(KeyEvent* keyEvent) const { + keyEvent->initialize( + mSharedMessage->deviceId, + mSharedMessage->nature, + mSharedMessage->key.action, + mSharedMessage->key.flags, + mSharedMessage->key.keyCode, + mSharedMessage->key.scanCode, + mSharedMessage->key.metaState, + mSharedMessage->key.repeatCount, + mSharedMessage->key.downTime, + mSharedMessage->key.eventTime); +} + +void InputConsumer::populateMotionEvent(MotionEvent* motionEvent) const { + motionEvent->initialize( + mSharedMessage->deviceId, + mSharedMessage->nature, + mSharedMessage->motion.action, + mSharedMessage->motion.edgeFlags, + mSharedMessage->motion.metaState, + mSharedMessage->motion.sampleData[0].coords[0].x, + mSharedMessage->motion.sampleData[0].coords[0].y, + mSharedMessage->motion.xPrecision, + mSharedMessage->motion.yPrecision, + mSharedMessage->motion.downTime, + mSharedMessage->motion.sampleData[0].eventTime, + mSharedMessage->motion.pointerCount, + mSharedMessage->motion.pointerIds, + mSharedMessage->motion.sampleData[0].coords); + + size_t sampleCount = mSharedMessage->motion.sampleCount; + if (sampleCount > 1) { + InputMessage::SampleData* sampleData = mSharedMessage->motion.sampleData; + size_t sampleDataStride = InputMessage::sampleDataStride( + mSharedMessage->motion.pointerCount); + + while (--sampleCount > 0) { + sampleData = InputMessage::sampleDataPtrIncrement(sampleData, sampleDataStride); + motionEvent->addSample(sampleData->eventTime, sampleData->coords); + } + } + + motionEvent->offsetLocation(mSharedMessage->motion.xOffset, + mSharedMessage->motion.yOffset); +} + +} // namespace android diff --git a/libs/ui/tests/Android.mk b/libs/ui/tests/Android.mk index 6cc4a5a..018f18d 100644 --- a/libs/ui/tests/Android.mk +++ b/libs/ui/tests/Android.mk @@ -1,16 +1,38 @@ +# Build the unit tests. LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= \ - region.cpp +test_src_files := \ + InputDispatcher_test.cpp LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ - libui + libEGL \ + libbinder \ + libpixelflinger \ + libhardware \ + libhardware_legacy \ + libui \ + libstlport -LOCAL_MODULE:= test-region +LOCAL_STATIC_LIBRARIES := \ + libgtest \ + libgtest_main -LOCAL_MODULE_TAGS := tests +LOCAL_C_INCLUDES := \ + bionic \ + bionic/libstdc++/include \ + external/gtest/include \ + external/stlport/stlport -include $(BUILD_EXECUTABLE) +LOCAL_MODULE_TAGS := eng tests + +$(foreach file,$(test_src_files), \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval include $(BUILD_EXECUTABLE)) \ +) + +# Build the manual test programs. +include $(call all-subdir-makefiles) diff --git a/libs/ui/tests/InputDispatcher_test.cpp b/libs/ui/tests/InputDispatcher_test.cpp new file mode 100644 index 0000000..3d92043 --- /dev/null +++ b/libs/ui/tests/InputDispatcher_test.cpp @@ -0,0 +1,19 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include <ui/InputDispatcher.h> +#include <gtest/gtest.h> + +namespace android { + +class InputDispatcherTest : public testing::Test { +public: +}; + +TEST_F(InputDispatcherTest, Dummy) { + SCOPED_TRACE("Trace"); + ASSERT_FALSE(true); +} + +} // namespace android diff --git a/libs/ui/tests/region/Android.mk b/libs/ui/tests/region/Android.mk new file mode 100644 index 0000000..6cc4a5a --- /dev/null +++ b/libs/ui/tests/region/Android.mk @@ -0,0 +1,16 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + region.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libui + +LOCAL_MODULE:= test-region + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) diff --git a/libs/ui/tests/region.cpp b/libs/ui/tests/region/region.cpp index ef15de9..ef15de9 100644 --- a/libs/ui/tests/region.cpp +++ b/libs/ui/tests/region/region.cpp diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index afecdcb..945b039 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -26,6 +26,8 @@ commonSources:= \ Debug.cpp \ FileMap.cpp \ Flattenable.cpp \ + PollLoop.cpp \ + Pool.cpp \ RefBase.cpp \ ResourceTypes.cpp \ SharedBuffer.cpp \ diff --git a/libs/utils/PollLoop.cpp b/libs/utils/PollLoop.cpp new file mode 100644 index 0000000..90a3e8b --- /dev/null +++ b/libs/utils/PollLoop.cpp @@ -0,0 +1,267 @@ +// +// Copyright 2010 The Android Open Source Project +// +// A select loop implementation. +// +#define LOG_TAG "PollLoop" + +//#define LOG_NDEBUG 0 + +// Debugs poll and wake interactions. +#define DEBUG_POLL_AND_WAKE 0 + +// Debugs callback registration and invocation. +#define DEBUG_CALLBACKS 1 + +#include <cutils/log.h> +#include <utils/PollLoop.h> + +#include <unistd.h> +#include <fcntl.h> + +namespace android { + +PollLoop::PollLoop() : + mPolling(false) { + openWakePipe(); +} + +PollLoop::~PollLoop() { + closeWakePipe(); +} + +void PollLoop::openWakePipe() { + int wakeFds[2]; + int result = pipe(wakeFds); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno); + + mWakeReadPipeFd = wakeFds[0]; + mWakeWritePipeFd = wakeFds[1]; + + result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d", + errno); + + result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d", + errno); + + // Add the wake pipe to the head of the request list with a null callback. + struct pollfd requestedFd; + requestedFd.fd = mWakeReadPipeFd; + requestedFd.events = POLLIN; + mRequestedFds.insertAt(requestedFd, 0); + + RequestedCallback requestedCallback; + requestedCallback.callback = NULL; + requestedCallback.data = NULL; + mRequestedCallbacks.insertAt(requestedCallback, 0); +} + +void PollLoop::closeWakePipe() { + close(mWakeReadPipeFd); + close(mWakeWritePipeFd); + + // Note: We don't need to remove the poll structure or callback entry because this + // method is currently only called by the destructor. +} + +bool PollLoop::pollOnce(int timeoutMillis) { + mLock.lock(); + mPolling = true; + mLock.unlock(); + + bool result; + size_t requestedCount = mRequestedFds.size(); + +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - waiting on %d fds", this, requestedCount); + for (size_t i = 0; i < requestedCount; i++) { + LOGD(" fd %d - events %d", mRequestedFds[i].fd, mRequestedFds[i].events); + } +#endif + + int respondedCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis); + + if (respondedCount == 0) { + // Timeout +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - timeout", this); +#endif + result = false; + goto Done; + } + + if (respondedCount < 0) { + // Error +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - error, errno=%d", this, errno); +#endif + if (errno != EINTR) { + LOGW("Poll failed with an unexpected error, errno=%d", errno); + } + result = false; + goto Done; + } + +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - handling responses from %d fds", this, respondedCount); + for (size_t i = 0; i < requestedCount; i++) { + LOGD(" fd %d - events %d, revents %d", mRequestedFds[i].fd, mRequestedFds[i].events, + mRequestedFds[i].revents); + } +#endif + + mPendingCallbacks.clear(); + for (size_t i = 0; i < requestedCount; i++) { + const struct pollfd& requestedFd = mRequestedFds.itemAt(i); + + short revents = requestedFd.revents; + if (revents) { + const RequestedCallback& requestedCallback = mRequestedCallbacks.itemAt(i); + Callback callback = requestedCallback.callback; + + if (callback) { + PendingCallback pendingCallback; + pendingCallback.fd = requestedFd.fd; + pendingCallback.events = requestedFd.revents; + pendingCallback.callback = callback; + pendingCallback.data = requestedCallback.data; + mPendingCallbacks.push(pendingCallback); + } else { + if (requestedFd.fd == mWakeReadPipeFd) { +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - awoken", this); +#endif + char buffer[16]; + ssize_t nRead; + do { + nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); + } while (nRead == sizeof(buffer)); + } else { +#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS + LOGD("%p ~ pollOnce - fd %d has no callback!", this, requestedFd.fd); +#endif + } + } + + respondedCount -= 1; + if (respondedCount == 0) { + break; + } + } + } + result = true; + +Done: + mLock.lock(); + mPolling = false; + mAwake.broadcast(); + mLock.unlock(); + + if (result) { + size_t pendingCount = mPendingCallbacks.size(); + for (size_t i = 0; i < pendingCount; i++) { + const PendingCallback& pendingCallback = mPendingCallbacks.itemAt(i); +#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS + LOGD("%p ~ pollOnce - invoking callback for fd %d", this, pendingCallback.fd); +#endif + + bool keep = pendingCallback.callback(pendingCallback.fd, pendingCallback.events, + pendingCallback.data); + if (! keep) { + removeCallback(pendingCallback.fd); + } + } + } + +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ pollOnce - done", this); +#endif + return result; +} + +void PollLoop::wake() { +#if DEBUG_POLL_AND_WAKE + LOGD("%p ~ wake", this); +#endif + + ssize_t nWrite = write(mWakeWritePipeFd, "W", 1); + if (nWrite != 1) { + if (errno != EAGAIN) { + LOGW("Could not write wake signal, errno=%d", errno); + } + } +} + +void PollLoop::setCallback(int fd, int events, Callback callback, void* data) { +#if DEBUG_CALLBACKS + LOGD("%p ~ setCallback - fd=%d, events=%d", this, fd, events); +#endif + + if (! events || ! callback) { + LOGE("Invalid attempt to set a callback with no selected poll events or no callback."); + removeCallback(fd); + return; + } + + wakeAndLock(); + + struct pollfd requestedFd; + requestedFd.fd = fd; + requestedFd.events = events; + + RequestedCallback requestedCallback; + requestedCallback.callback = callback; + requestedCallback.data = data; + + ssize_t index = getRequestIndexLocked(fd); + if (index < 0) { + mRequestedFds.push(requestedFd); + mRequestedCallbacks.push(requestedCallback); + } else { + mRequestedFds.replaceAt(requestedFd, size_t(index)); + mRequestedCallbacks.replaceAt(requestedCallback, size_t(index)); + } + + mLock.unlock(); +} + +bool PollLoop::removeCallback(int fd) { +#if DEBUG_CALLBACKS + LOGD("%p ~ removeCallback - fd=%d", this, fd); +#endif + + wakeAndLock(); + + ssize_t index = getRequestIndexLocked(fd); + if (index >= 0) { + mRequestedFds.removeAt(size_t(index)); + mRequestedCallbacks.removeAt(size_t(index)); + } + + mLock.unlock(); + return index >= 0; +} + +ssize_t PollLoop::getRequestIndexLocked(int fd) { + size_t requestCount = mRequestedFds.size(); + + for (size_t i = 0; i < requestCount; i++) { + if (mRequestedFds.itemAt(i).fd == fd) { + return i; + } + } + + return -1; +} + +void PollLoop::wakeAndLock() { + mLock.lock(); + while (mPolling) { + wake(); + mAwake.wait(mLock); + } +} + +} // namespace android diff --git a/libs/utils/Pool.cpp b/libs/utils/Pool.cpp new file mode 100644 index 0000000..8f18cb9 --- /dev/null +++ b/libs/utils/Pool.cpp @@ -0,0 +1,37 @@ +// +// Copyright 2010 The Android Open Source Project +// +// A simple memory pool. +// +#define LOG_TAG "Pool" + +//#define LOG_NDEBUG 0 + +#include <cutils/log.h> +#include <utils/Pool.h> + +#include <stdlib.h> + +namespace android { + +// TODO Provide a real implementation of a pool. This is just a stub for initial development. + +PoolImpl::PoolImpl(size_t objSize) : + mObjSize(objSize) { +} + +PoolImpl::~PoolImpl() { +} + +void* PoolImpl::allocImpl() { + void* ptr = malloc(mObjSize); + LOG_ALWAYS_FATAL_IF(ptr == NULL, "Cannot allocate new pool object."); + return ptr; +} + +void PoolImpl::freeImpl(void* obj) { + LOG_ALWAYS_FATAL_IF(obj == NULL, "Caller attempted to free NULL pool object."); + return free(obj); +} + +} // namespace android diff --git a/libs/utils/StopWatch.cpp b/libs/utils/StopWatch.cpp index 68a1c52..b5dda2f 100644 --- a/libs/utils/StopWatch.cpp +++ b/libs/utils/StopWatch.cpp @@ -30,10 +30,9 @@ namespace android { StopWatch::StopWatch(const char *name, int clock, uint32_t flags) - : mName(name), mClock(clock), mFlags(flags), - mStartTime(0), mNumLaps(0) + : mName(name), mClock(clock), mFlags(flags) { - mStartTime = systemTime(mClock); + reset(); } StopWatch::~StopWatch() @@ -72,6 +71,12 @@ nsecs_t StopWatch::elapsedTime() const return systemTime(mClock) - mStartTime; } +void StopWatch::reset() +{ + mNumLaps = 0; + mStartTime = systemTime(mClock); +} + /*****************************************************************************/ diff --git a/libs/utils/VectorImpl.cpp b/libs/utils/VectorImpl.cpp index 0322af7..b09c6ca 100644 --- a/libs/utils/VectorImpl.cpp +++ b/libs/utils/VectorImpl.cpp @@ -108,13 +108,7 @@ size_t VectorImpl::capacity() const ssize_t VectorImpl::insertVectorAt(const VectorImpl& vector, size_t index) { - if (index > size()) - return BAD_INDEX; - void* where = _grow(index, vector.size()); - if (where) { - _do_copy(where, vector.arrayImpl(), vector.size()); - } - return where ? index : (ssize_t)NO_MEMORY; + return insertAt(vector.arrayImpl(), index, vector.size()); } ssize_t VectorImpl::appendVector(const VectorImpl& vector) @@ -226,9 +220,9 @@ ssize_t VectorImpl::add() return add(0); } -ssize_t VectorImpl::add(const void* item) +ssize_t VectorImpl::add(const void* item, size_t numItems) { - return insertAt(item, size()); + return insertAt(item, size(), numItems); } ssize_t VectorImpl::replaceAt(size_t index) diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk new file mode 100644 index 0000000..45e8061 --- /dev/null +++ b/libs/utils/tests/Android.mk @@ -0,0 +1,33 @@ +# Build the unit tests. +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +test_src_files := \ + PollLoop_test.cpp + +LOCAL_SHARED_LIBRARIES := \ + libz \ + liblog \ + libcutils \ + libutils \ + libstlport + +LOCAL_STATIC_LIBRARIES := \ + libgtest \ + libgtest_main + +LOCAL_C_INCLUDES := \ + external/zlib \ + external/icu4c/common \ + bionic \ + bionic/libstdc++/include \ + external/gtest/include \ + external/stlport/stlport + +LOCAL_MODULE_TAGS := eng tests + +$(foreach file,$(test_src_files), \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval include $(BUILD_EXECUTABLE)) \ +) diff --git a/libs/utils/tests/PollLoop_test.cpp b/libs/utils/tests/PollLoop_test.cpp new file mode 100644 index 0000000..6c719c8 --- /dev/null +++ b/libs/utils/tests/PollLoop_test.cpp @@ -0,0 +1,398 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include <utils/PollLoop.h> +#include <utils/Timers.h> +#include <utils/StopWatch.h> +#include <gtest/gtest.h> +#include <unistd.h> +#include <time.h> + +#include "TestHelpers.h" + +// # of milliseconds to fudge stopwatch measurements +#define TIMING_TOLERANCE_MS 25 + +namespace android { + +class Pipe { +public: + int sendFd; + int receiveFd; + + Pipe() { + int fds[2]; + ::pipe(fds); + + receiveFd = fds[0]; + sendFd = fds[1]; + } + + ~Pipe() { + ::close(sendFd); + ::close(receiveFd); + } + + bool writeSignal() { + return ::write(sendFd, "*", 1) == 1; + } + + bool readSignal() { + char buf[1]; + return ::read(receiveFd, buf, 1) == 1; + } +}; + +class DelayedWake : public DelayedTask { + sp<PollLoop> mPollLoop; + +public: + DelayedWake(int delayMillis, const sp<PollLoop> pollLoop) : + DelayedTask(delayMillis), mPollLoop(pollLoop) { + } + +protected: + virtual void doTask() { + mPollLoop->wake(); + } +}; + +class DelayedWriteSignal : public DelayedTask { + Pipe* mPipe; + +public: + DelayedWriteSignal(int delayMillis, Pipe* pipe) : + DelayedTask(delayMillis), mPipe(pipe) { + } + +protected: + virtual void doTask() { + mPipe->writeSignal(); + } +}; + +class CallbackHandler { +public: + void setCallback(const sp<PollLoop>& pollLoop, int fd, int events) { + pollLoop->setCallback(fd, events, staticHandler, this); + } + +protected: + virtual ~CallbackHandler() { } + + virtual bool handler(int fd, int events) = 0; + +private: + static bool staticHandler(int fd, int events, void* data) { + return static_cast<CallbackHandler*>(data)->handler(fd, events); + } +}; + +class StubCallbackHandler : public CallbackHandler { +public: + bool nextResult; + int callbackCount; + + int fd; + int events; + + StubCallbackHandler(bool nextResult) : nextResult(nextResult), + callbackCount(0), fd(-1), events(-1) { + } + +protected: + virtual bool handler(int fd, int events) { + callbackCount += 1; + this->fd = fd; + this->events = events; + return nextResult; + } +}; + +class PollLoopTest : public testing::Test { +protected: + sp<PollLoop> mPollLoop; + + virtual void SetUp() { + mPollLoop = new PollLoop(); + } + + virtual void TearDown() { + mPollLoop.clear(); + } +}; + + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNotAwoken_WaitsForTimeoutAndReturnsFalse) { + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenBeforeWaiting_ImmediatelyReturnsTrue) { + mPollLoop->wake(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because wake() was called before waiting"; + EXPECT_TRUE(result) + << "pollOnce result should be true because loop was awoken"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenWhileWaiting_PromptlyReturnsTrue) { + sp<DelayedWake> delayedWake = new DelayedWake(100, mPollLoop); + delayedWake->run(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal wake delay"; + EXPECT_TRUE(result) + << "pollOnce result should be true because loop was awoken"; +} + +TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoRegisteredFDs_ImmediatelyReturnsFalse) { + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; +} + +TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoSignalledFDs_ImmediatelyReturnsFalse) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not have been invoked because FD was not signalled"; +} + +TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndSignalledFD_ImmediatelyInvokesCallbackAndReturnsTrue) { + Pipe pipe; + StubCallbackHandler handler(true); + + ASSERT_TRUE(pipe.writeSignal()); + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(POLL_IN, handler.events) + << "callback should have received POLL_IN as events"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNoSignalledFDs_WaitsForTimeoutAndReturnsFalse) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not have been invoked because FD was not signalled"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDBeforeWaiting_ImmediatelyInvokesCallbackAndReturnsTrue) { + Pipe pipe; + StubCallbackHandler handler(true); + + pipe.writeSignal(); + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(POLL_IN, handler.events) + << "callback should have received POLL_IN as events"; +} + +TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDWhileWaiting_PromptlyInvokesCallbackAndReturnsTrue) { + Pipe pipe; + StubCallbackHandler handler(true); + sp<DelayedWriteSignal> delayedWriteSignal = new DelayedWriteSignal(100, & pipe); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + delayedWriteSignal->run(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal signal delay"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(POLL_IN, handler.events) + << "callback should have received POLL_IN as events"; +} + +TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedThenRemoved_CallbackShouldNotBeInvoked) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + pipe.writeSignal(); // would cause FD to be considered signalled + mPollLoop->removeCallback(pipe.receiveFd); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout because FD was no longer registered"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not be invoked"; +} + +TEST_F(PollLoopTest, PollOnce_WhenCallbackReturnsFalse_CallbackShouldNotBeInvokedAgainLater) { + Pipe pipe; + StubCallbackHandler handler(false); + + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + // First loop: Callback is registered and FD is signalled. + pipe.writeSignal(); + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal zero because FD was already signalled"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked"; + + // Second loop: Callback is no longer registered and FD is signalled. + pipe.writeSignal(); + + stopWatch.reset(); + result = mPollLoop->pollOnce(0); + elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal zero because timeout was zero"; + EXPECT_FALSE(result) + << "pollOnce result should be false because timeout occurred"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should not be invoked this time"; +} + +TEST_F(PollLoopTest, RemoveCallback_WhenCallbackNotAdded_ReturnsFalse) { + bool result = mPollLoop->removeCallback(1); + + EXPECT_FALSE(result) + << "removeCallback should return false because FD not registered"; +} + +TEST_F(PollLoopTest, RemoveCallback_WhenCallbackAddedThenRemovedTwice_ReturnsTrueFirstTimeAndReturnsFalseSecondTime) { + Pipe pipe; + StubCallbackHandler handler(false); + handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + + // First time. + bool result = mPollLoop->removeCallback(pipe.receiveFd); + + EXPECT_TRUE(result) + << "removeCallback should return true first time because FD was registered"; + + // Second time. + result = mPollLoop->removeCallback(pipe.receiveFd); + + EXPECT_FALSE(result) + << "removeCallback should return false second time because FD was no longer registered"; +} + +TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedTwice_OnlySecondCallbackShouldBeInvoked) { + Pipe pipe; + StubCallbackHandler handler1(true); + StubCallbackHandler handler2(true); + + handler1.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); + handler2.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); // replace it + pipe.writeSignal(); // would cause FD to be considered signalled + + StopWatch stopWatch("pollOnce"); + bool result = mPollLoop->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_TRUE(pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because FD was already signalled"; + EXPECT_TRUE(result) + << "pollOnce result should be true because FD was signalled"; + EXPECT_EQ(0, handler1.callbackCount) + << "original handler callback should not be invoked because it was replaced"; + EXPECT_EQ(1, handler2.callbackCount) + << "replacement handler callback should be invoked"; +} + + +} // namespace android diff --git a/libs/utils/tests/TestHelpers.h b/libs/utils/tests/TestHelpers.h new file mode 100644 index 0000000..e55af3c --- /dev/null +++ b/libs/utils/tests/TestHelpers.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#ifndef TESTHELPERS_H +#define TESTHELPERS_H + +#include <utils/threads.h> + +namespace android { + +class DelayedTask : public Thread { + int mDelayMillis; + +public: + DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { } + +protected: + virtual ~DelayedTask() { } + + virtual void doTask() = 0; + + virtual bool threadLoop() { + usleep(mDelayMillis * 1000); + doTask(); + return false; + } +}; + +} // namespace android + +#endif // TESTHELPERS_H diff --git a/native/include/android/input.h b/native/include/android/input.h new file mode 100644 index 0000000..ee2f664 --- /dev/null +++ b/native/include/android/input.h @@ -0,0 +1,500 @@ +/* + * 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. + */ + +#ifndef _ANDROID_INPUT_H +#define _ANDROID_INPUT_H + +/****************************************************************** + * + * IMPORTANT NOTICE: + * + * This file is part of Android's set of stable system headers + * exposed by the Android NDK (Native Development Kit). + * + * Third-party source AND binary code relies on the definitions + * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES. + * + * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES) + * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS + * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY + * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES + */ + +/* + * Structures and functions to receive and process input events in + * native code. + * + * NOTE: These functions MUST be implemented by /system/lib/libui.so + */ + +#include <sys/types.h> +#include <android/keycodes.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Input device classes. + */ +enum { + /* The input device is a keyboard. */ + INPUT_DEVICE_CLASS_KEYBOARD = 0x00000001, + + /* The input device is an alpha-numeric keyboard (not just a dial pad). */ + INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002, + + /* The input device is a touchscreen (either single-touch or multi-touch). */ + INPUT_DEVICE_CLASS_TOUCHSCREEN = 0x00000004, + + /* The input device is a trackball. */ + INPUT_DEVICE_CLASS_TRACKBALL = 0x00000008, + + /* The input device is a multi-touch touchscreen. */ + INPUT_DEVICE_CLASS_TOUCHSCREEN_MT= 0x00000010, + + /* The input device is a directional pad. */ + INPUT_DEVICE_CLASS_DPAD = 0x00000020 +}; + +/* + * Key states (may be returned by queries about the current state of a + * particular key code, scan code or switch). + * + * XXX should we call this BUTTON_STATE_XXX? + */ +enum { + /* The key state is unknown or the requested key itself is not supported. */ + KEY_STATE_UNKNOWN = -1, + + /* The key is up. */ + KEY_STATE_UP = 0, + + /* The key is down. */ + KEY_STATE_DOWN = 1, + + /* The key is down but is a virtual key press that is being emulated by the system. */ + KEY_STATE_VIRTUAL = 2 +}; + +/* + * Meta key / modifer state. + */ +enum { + /* No meta keys are pressed. */ + META_NONE = 0, + + /* This mask is used to check whether one of the ALT meta keys is pressed. */ + META_ALT_ON = 0x02, + + /* This mask is used to check whether the left ALT meta key is pressed. */ + META_ALT_LEFT_ON = 0x10, + + /* This mask is used to check whether the right ALT meta key is pressed. */ + META_ALT_RIGHT_ON = 0x20, + + /* This mask is used to check whether one of the SHIFT meta keys is pressed. */ + META_SHIFT_ON = 0x01, + + /* This mask is used to check whether the left SHIFT meta key is pressed. */ + META_SHIFT_LEFT_ON = 0x40, + + /* This mask is used to check whether the right SHIFT meta key is pressed. */ + META_SHIFT_RIGHT_ON = 0x80, + + /* This mask is used to check whether the SYM meta key is pressed. */ + META_SYM_ON = 0x04 +}; + +/* + * Input events. + * + * Input events are opaque structures. Use the provided accessors functions to + * read their properties. + */ +struct input_event_t; +typedef struct input_event_t input_event_t; + +/* + * Input event types. + */ +enum { + /* Indicates that the input event is a key event. */ + INPUT_EVENT_TYPE_KEY = 1, + + /* Indicates that the input event is a motion event. */ + INPUT_EVENT_TYPE_MOTION = 2 +}; + +/* + * Key event actions. + */ +enum { + /* The key has been pressed down. */ + KEY_EVENT_ACTION_DOWN = 0, + + /* The key has been released. */ + KEY_EVENT_ACTION_UP = 1, + + /* Multiple duplicate key events have occurred in a row, or a complex string is + * being delivered. The repeat_count property of the key event contains the number + * of times the given key code should be executed. + */ + KEY_EVENT_ACTION_MULTIPLE = 2 +}; + +/* + * Key event flags. + */ +enum { + /* This mask is set if the device woke because of this key event. */ + KEY_EVENT_FLAG_WOKE_HERE = 0x1, + + /* This mask is set if the key event was generated by a software keyboard. */ + KEY_EVENT_FLAG_SOFT_KEYBOARD = 0x2, + + /* This mask is set if we don't want the key event to cause us to leave touch mode. */ + KEY_EVENT_FLAG_KEEP_TOUCH_MODE = 0x4, + + /* This mask is set if an event was known to come from a trusted part + * of the system. That is, the event is known to come from the user, + * and could not have been spoofed by a third party component. */ + KEY_EVENT_FLAG_FROM_SYSTEM = 0x8, + + /* This mask is used for compatibility, to identify enter keys that are + * coming from an IME whose enter key has been auto-labelled "next" or + * "done". This allows TextView to dispatch these as normal enter keys + * for old applications, but still do the appropriate action when + * receiving them. */ + KEY_EVENT_FLAG_EDITOR_ACTION = 0x10, + + /* When associated with up key events, this indicates that the key press + * has been canceled. Typically this is used with virtual touch screen + * keys, where the user can slide from the virtual key area on to the + * display: in that case, the application will receive a canceled up + * event and should not perform the action normally associated with the + * key. Note that for this to work, the application can not perform an + * action for a key until it receives an up or the long press timeout has + * expired. */ + KEY_EVENT_FLAG_CANCELED = 0x20, + + /* This key event was generated by a virtual (on-screen) hard key area. + * Typically this is an area of the touchscreen, outside of the regular + * display, dedicated to "hardware" buttons. */ + KEY_EVENT_FLAG_VIRTUAL_HARD_KEY = 0x40, + + /* This flag is set for the first key repeat that occurs after the + * long press timeout. */ + KEY_EVENT_FLAG_LONG_PRESS = 0x80, + + /* Set when a key event has KEY_EVENT_FLAG_CANCELED set because a long + * press action was executed while it was down. */ + KEY_EVENT_FLAG_CANCELED_LONG_PRESS = 0x100, + + /* Set for KEY_EVENT_ACTION_UP when this event's key code is still being + * tracked from its initial down. That is, somebody requested that tracking + * started on the key down and a long press has not caused + * the tracking to be canceled. */ + KEY_EVENT_FLAG_TRACKING = 0x200 +}; + +/* + * Motion event actions. + */ + +/* Bit shift for the action bits holding the pointer index as + * defined by MOTION_EVENT_ACTION_POINTER_INDEX_MASK. + */ +#define MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT 8 + +enum { + /* Bit mask of the parts of the action code that are the action itself. + */ + MOTION_EVENT_ACTION_MASK = 0xff, + + /* Bits in the action code that represent a pointer index, used with + * MOTION_EVENT_ACTION_POINTER_DOWN and MOTION_EVENT_ACTION_POINTER_UP. Shifting + * down by MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer + * index where the data for the pointer going up or down can be found. + */ + MOTION_EVENT_ACTION_POINTER_INDEX_MASK = 0xff00, + + /* A pressed gesture has started, the motion contains the initial starting location. + */ + MOTION_EVENT_ACTION_DOWN = 0, + + /* A pressed gesture has finished, the motion contains the final release location + * as well as any intermediate points since the last down or move event. + */ + MOTION_EVENT_ACTION_UP = 1, + + /* A change has happened during a press gesture (between MOTION_EVENT_ACTION_DOWN and + * MOTION_EVENT_ACTION_UP). The motion contains the most recent point, as well as + * any intermediate points since the last down or move event. + */ + MOTION_EVENT_ACTION_MOVE = 2, + + /* The current gesture has been aborted. + * You will not receive any more points in it. You should treat this as + * an up event, but not perform any action that you normally would. + */ + MOTION_EVENT_ACTION_CANCEL = 3, + + /* A movement has happened outside of the normal bounds of the UI element. + * This does not provide a full gesture, but only the initial location of the movement/touch. + */ + MOTION_EVENT_ACTION_OUTSIDE = 4, + + /* A non-primary pointer has gone down. + * The bits in MOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed. + */ + MOTION_EVENT_ACTION_POINTER_DOWN = 5, + + /* A non-primary pointer has gone up. + * The bits in MOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed. + */ + MOTION_EVENT_ACTION_POINTER_UP = 6 +}; + +/* + * Motion event edge touch flags. + */ +enum { + /* No edges intersected */ + MOTION_EVENT_EDGE_FLAG_NONE = 0, + + /* Flag indicating the motion event intersected the top edge of the screen. */ + MOTION_EVENT_EDGE_FLAG_TOP = 0x01, + + /* Flag indicating the motion event intersected the bottom edge of the screen. */ + MOTION_EVENT_EDGE_FLAG_BOTTOM = 0x02, + + /* Flag indicating the motion event intersected the left edge of the screen. */ + MOTION_EVENT_EDGE_FLAG_LEFT = 0x04, + + /* Flag indicating the motion event intersected the right edge of the screen. */ + MOTION_EVENT_EDGE_FLAG_RIGHT = 0x08 +}; + +/* + * Specifies the logical nature of an input event. + * For example, the nature distinguishes between motion events that represent touches and + * those that represent trackball moves. + * + * XXX This concept is tentative. Another idea would be to associate events with logical + * controllers rather than physical devices. The interpretation of an event would + * be made with respect to the nature of the controller that is considered the logical + * source of an event. The decoupling is beneficial since multiple physical (and virtual) + * devices could be responsible for producing events that would be associated with + * various logical controllers. For example, the hard keyboard, on screen keyboard, + * and peripheral keyboard could be mapped onto a single logical "keyboard" controller + * (or treated independently, if desired). + */ +enum { + INPUT_EVENT_NATURE_KEY = 1, + INPUT_EVENT_NATURE_TOUCH = 2, + INPUT_EVENT_NATURE_TRACKBALL = 3 +}; + +/* + * Input event accessors. + * + * Note that most functions can only be used on input events that are of a given type. + * Calling these functions on input events of other types will yield undefined behavior. + */ + +/*** Accessors for all input events. ***/ + +/* Get the input event type. */ +int32_t input_event_get_type(const input_event_t* event); + +/* Get the id for the device that an input event came from. + * + * Input events can be generated by multiple different input devices. + * Use the input device id to obtain information about the input + * device that was responsible for generating a particular event. + * + * An input device id of 0 indicates that the event didn't come from a physical device; + * other numbers are arbitrary and you shouldn't depend on the values. + * Use the provided input device query API to obtain information about input devices. + */ +int32_t input_event_get_device_id(const input_event_t* event); + +/* Get the input event nature. */ +int32_t input_event_get_nature(const input_event_t* event); + +/*** Accessors for key events only. ***/ + +/* Get the key event action. */ +int32_t key_event_get_action(const input_event_t* key_event); + +/* Get the key event flags. */ +int32_t key_event_get_flags(const input_event_t* key_event); + +/* Get the key code of the key event. + * This is the physical key that was pressed, not the Unicode character. */ +int32_t key_event_get_key_code(const input_event_t* key_event); + +/* Get the hardware key id of this key event. + * These values are not reliable and vary from device to device. */ +int32_t key_event_get_scan_code(const input_event_t* key_event); + +/* Get the meta key state. */ +int32_t key_event_get_meta_state(const input_event_t* key_event); + +/* Get the repeat count of the event. + * For both key up an key down events, this is the number of times the key has + * repeated with the first down starting at 0 and counting up from there. For + * multiple key events, this is the number of down/up pairs that have occurred. */ +int32_t key_event_get_repeat_count(const input_event_t* key_event); + +/* Get the time of the most recent key down event, in the + * java.lang.System.nanoTime() time base. If this is a down event, + * this will be the same as eventTime. + * Note that when chording keys, this value is the down time of the most recently + * pressed key, which may not be the same physical key of this event. */ +int64_t key_event_get_down_time(const input_event_t* key_event); + +/* Get the time this event occurred, in the + * java.lang.System.nanoTime() time base. */ +int64_t key_event_get_event_time(const input_event_t* key_event); + +/*** Accessors for motion events only. ***/ + +/* Get the combined motion event action code and pointer index. */ +int32_t motion_event_get_action(const input_event_t* motion_event); + +/* Get the state of any meta / modifier keys that were in effect when the + * event was generated. */ +int32_t motion_event_get_meta_state(const input_event_t* motion_event); + +/* Get a bitfield indicating which edges, if any, were touched by this motion event. + * For touch events, clients can use this to determine if the user's finger was + * touching the edge of the display. */ +int32_t motion_event_get_edge_flags(const input_event_t* motion_event); + +/* Get the time when the user originally pressed down to start a stream of + * position events, in the java.lang.System.nanoTime() time base. */ +int64_t motion_event_get_down_time(const input_event_t* motion_event); + +/* Get the time when this specific event was generated, + * in the java.lang.System.nanoTime() time base. */ +int64_t motion_event_get_event_time(const input_event_t* motion_event); + +/* Get the precision of the X coordinates being reported. + * You can multiply this number with an X coordinate sample to find the + * actual hardware value of the X coordinate. */ +float motion_event_get_x_precision(const input_event_t* motion_event); + +/* Get the precision of the Y coordinates being reported. + * You can multiply this number with a Y coordinate sample to find the + * actual hardware value of the Y coordinate. */ +float motion_event_get_y_precision(const input_event_t* motion_event); + +/* Get the number of pointers of data contained in this event. + * Always >= 1. */ +size_t motion_event_get_pointer_count(const input_event_t* motion_event); + +/* Get the pointer identifier associated with a particular pointer + * data index is this event. The identifier tells you the actual pointer + * number associated with the data, accounting for individual pointers + * going up and down since the start of the current gesture. */ +int32_t motion_event_get_pointer_id(const input_event_t* motion_event, size_t pointer_index); + +/* Get the original raw X coordinate of this event. For touch + * events on the screen, this is the original location of the event + * on the screen, before it had been adjusted for the containing window + * and views. */ +float motion_event_get_raw_x(const input_event_t* motion_event); + +/* Get the original raw X coordinate of this event. For touch + * events on the screen, this is the original location of the event + * on the screen, before it had been adjusted for the containing window + * and views. */ +float motion_event_get_raw_y(const input_event_t* motion_event); + +/* Get the current X coordinate of this event for the given pointer index. + * Whole numbers are pixels; the value may have a fraction for input devices + * that are sub-pixel precise. */ +float motion_event_get_x(const input_event_t* motion_event, size_t pointer_index); + +/* Get the current Y coordinate of this event for the given pointer index. + * Whole numbers are pixels; the value may have a fraction for input devices + * that are sub-pixel precise. */ +float motion_event_get_y(const input_event_t* motion_event, size_t pointer_index); + +/* Get the current pressure of this event for the given pointer index. + * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure), + * however values higher than 1 may be generated depending on the calibration of + * the input device. */ +float motion_event_get_pressure(const input_event_t* motion_event, size_t pointer_index); + +/* Get the current scaled value of the approximate size for the given pointer index. + * This represents some approximation of the area of the screen being + * pressed; the actual value in pixels corresponding to the + * touch is normalized with the device specific range of values + * and scaled to a value between 0 and 1. The value of size can be used to + * determine fat touch events. */ +float motion_event_get_size(const input_event_t* motion_event, size_t pointer_index); + +/* Get the number of historical points in this event. These are movements that + * have occurred between this event and the previous event. This only applies + * to MOTION_EVENT_ACTION_MOVE events -- all other actions will have a size of 0. + * Historical samples are indexed from oldest to newest. */ +size_t motion_event_get_history_size(const input_event_t* motion_event); + +/* Get the time that a historical movement occurred between this event and + * the previous event, in the java.lang.System.nanoTime() time base. */ +int64_t motion_event_get_historical_event_time(input_event_t* motion_event, + size_t history_index); + +/* Get the historical X coordinate of this event for the given pointer index that + * occurred between this event and the previous motion event. + * Whole numbers are pixels; the value may have a fraction for input devices + * that are sub-pixel precise. */ +float motion_event_get_historical_x(input_event_t* motion_event, size_t pointer_index, + size_t history_index); + +/* Get the historical Y coordinate of this event for the given pointer index that + * occurred between this event and the previous motion event. + * Whole numbers are pixels; the value may have a fraction for input devices + * that are sub-pixel precise. */ +float motion_event_get_historical_y(input_event_t* motion_event, size_t pointer_index, + size_t history_index); + +/* Get the historical pressure of this event for the given pointer index that + * occurred between this event and the previous motion event. + * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure), + * however values higher than 1 may be generated depending on the calibration of + * the input device. */ +float motion_event_get_historical_pressure(input_event_t* motion_event, size_t pointer_index, + size_t history_index); + +/* Get the current scaled value of the approximate size for the given pointer index that + * occurred between this event and the previous motion event. + * This represents some approximation of the area of the screen being + * pressed; the actual value in pixels corresponding to the + * touch is normalized with the device specific range of values + * and scaled to a value between 0 and 1. The value of size can be used to + * determine fat touch events. */ +float motion_event_get_historical_size(input_event_t* motion_event, size_t pointer_index, + size_t history_index); + +#ifdef __cplusplus +} +#endif + +#endif // _ANDROID_INPUT_H diff --git a/native/include/android/keycodes.h b/native/include/android/keycodes.h new file mode 100644 index 0000000..36855c5 --- /dev/null +++ b/native/include/android/keycodes.h @@ -0,0 +1,158 @@ +/* + * 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. + */ + +#ifndef _ANDROID_KEYCODES_H +#define _ANDROID_KEYCODES_H + +/****************************************************************** + * + * IMPORTANT NOTICE: + * + * This file is part of Android's set of stable system headers + * exposed by the Android NDK (Native Development Kit). + * + * Third-party source AND binary code relies on the definitions + * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES. + * + * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES) + * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS + * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY + * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES + */ + +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Key codes. + * + * XXX: The declarations in <ui/KeycodeLabel.h> should be updated to use these instead. + * We should probably move this into android/keycodes.h and add some new API for + * getting labels so that we can remove the other tables also in KeycodeLabel.h. + */ +enum { + KEYCODE_UNKNOWN = 0, + KEYCODE_SOFT_LEFT = 1, + KEYCODE_SOFT_RIGHT = 2, + KEYCODE_HOME = 3, + KEYCODE_BACK = 4, + KEYCODE_CALL = 5, + KEYCODE_ENDCALL = 6, + KEYCODE_0 = 7, + KEYCODE_1 = 8, + KEYCODE_2 = 9, + KEYCODE_3 = 10, + KEYCODE_4 = 11, + KEYCODE_5 = 12, + KEYCODE_6 = 13, + KEYCODE_7 = 14, + KEYCODE_8 = 15, + KEYCODE_9 = 16, + KEYCODE_STAR = 17, + KEYCODE_POUND = 18, + KEYCODE_DPAD_UP = 19, + KEYCODE_DPAD_DOWN = 20, + KEYCODE_DPAD_LEFT = 21, + KEYCODE_DPAD_RIGHT = 22, + KEYCODE_DPAD_CENTER = 23, + KEYCODE_VOLUME_UP = 24, + KEYCODE_VOLUME_DOWN = 25, + KEYCODE_POWER = 26, + KEYCODE_CAMERA = 27, + KEYCODE_CLEAR = 28, + KEYCODE_A = 29, + KEYCODE_B = 30, + KEYCODE_C = 31, + KEYCODE_D = 32, + KEYCODE_E = 33, + KEYCODE_F = 34, + KEYCODE_G = 35, + KEYCODE_H = 36, + KEYCODE_I = 37, + KEYCODE_J = 38, + KEYCODE_K = 39, + KEYCODE_L = 40, + KEYCODE_M = 41, + KEYCODE_N = 42, + KEYCODE_O = 43, + KEYCODE_P = 44, + KEYCODE_Q = 45, + KEYCODE_R = 46, + KEYCODE_S = 47, + KEYCODE_T = 48, + KEYCODE_U = 49, + KEYCODE_V = 50, + KEYCODE_W = 51, + KEYCODE_X = 52, + KEYCODE_Y = 53, + KEYCODE_Z = 54, + KEYCODE_COMMA = 55, + KEYCODE_PERIOD = 56, + KEYCODE_ALT_LEFT = 57, + KEYCODE_ALT_RIGHT = 58, + KEYCODE_SHIFT_LEFT = 59, + KEYCODE_SHIFT_RIGHT = 60, + KEYCODE_TAB = 61, + KEYCODE_SPACE = 62, + KEYCODE_SYM = 63, + KEYCODE_EXPLORER = 64, + KEYCODE_ENVELOPE = 65, + KEYCODE_ENTER = 66, + KEYCODE_DEL = 67, + KEYCODE_GRAVE = 68, + KEYCODE_MINUS = 69, + KEYCODE_EQUALS = 70, + KEYCODE_LEFT_BRACKET = 71, + KEYCODE_RIGHT_BRACKET = 72, + KEYCODE_BACKSLASH = 73, + KEYCODE_SEMICOLON = 74, + KEYCODE_APOSTROPHE = 75, + KEYCODE_SLASH = 76, + KEYCODE_AT = 77, + KEYCODE_NUM = 78, + KEYCODE_HEADSETHOOK = 79, + KEYCODE_FOCUS = 80, // *Camera* focus + KEYCODE_PLUS = 81, + KEYCODE_MENU = 82, + KEYCODE_NOTIFICATION = 83, + KEYCODE_SEARCH = 84, + KEYCODE_MEDIA_PLAY_PAUSE= 85, + KEYCODE_MEDIA_STOP = 86, + KEYCODE_MEDIA_NEXT = 87, + KEYCODE_MEDIA_PREVIOUS = 88, + KEYCODE_MEDIA_REWIND = 89, + KEYCODE_MEDIA_FAST_FORWARD = 90, + KEYCODE_MUTE = 91, + KEYCODE_PAGE_UP = 92, + KEYCODE_PAGE_DOWN = 93 + + /* NOTE: If you add a new keycode here you must also add it to: + * native/include/android/keycodes.h + * frameworks/base/include/ui/KeycodeLabels.h + * frameworks/base/core/java/android/view/KeyEvent.java + * tools/puppet_master/PuppetMaster.nav_keys.py + * frameworks/base/core/res/res/values/attrs.xml + */ +}; + +#ifdef __cplusplus +} +#endif + +#endif // _ANDROID_KEYCODES_H diff --git a/policy/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/com/android/internal/policy/impl/KeyguardViewMediator.java index eb61f5e..88203c3 100644 --- a/policy/com/android/internal/policy/impl/KeyguardViewMediator.java +++ b/policy/com/android/internal/policy/impl/KeyguardViewMediator.java @@ -86,7 +86,7 @@ import android.view.WindowManagerPolicy; * This class is created by the initialization routine of the {@link WindowManagerPolicy}, * and runs on its thread. The keyguard UI is created from that thread in the * constructor of this class. The apis may be called from other threads, including the - * {@link com.android.server.KeyInputQueue}'s and {@link android.view.WindowManager}'s. + * {@link com.android.server.InputManager}'s and {@link android.view.WindowManager}'s. * Therefore, methods on this class are synchronized, and any action that is pointed * directly to the keyguard UI is posted to a {@link Handler} to ensure it is taken on the UI * thread of the keyguard. diff --git a/policy/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/com/android/internal/policy/impl/PhoneWindowManager.java index d152bc4..a01e25b 100755 --- a/policy/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/com/android/internal/policy/impl/PhoneWindowManager.java @@ -1641,6 +1641,36 @@ public class PhoneWindowManager implements WindowManagerPolicy { } return false; } + + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { + // lid changed state + mLidOpen = lidOpen; + boolean awakeNow = mKeyguardMediator.doLidChangeTq(mLidOpen); + updateRotation(Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE); + if (awakeNow) { + // If the lid opening and we don't have to keep the + // keyguard up, then we can turn on the screen + // immediately. + mKeyguardMediator.pokeWakelock(); + } else if (keyguardIsShowingTq()) { + if (mLidOpen) { + // If we are opening the lid and not hiding the + // keyguard, then we need to have it turn on the + // screen once it is shown. + mKeyguardMediator.onWakeKeyWhenKeyguardShowingTq( + KeyEvent.KEYCODE_POWER); + } + } else { + // Light up the keyboard if we are sliding up. + if (mLidOpen) { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false, + LocalPowerManager.BUTTON_EVENT); + } else { + mPowerManager.userActivity(SystemClock.uptimeMillis(), false, + LocalPowerManager.OTHER_EVENT); + } + } + } /** {@inheritDoc} */ 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); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index 4201e80..f91f601 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -46,6 +46,7 @@ import android.os.RemoteException; import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.BridgeInflater; +import android.view.InputChannel; import android.view.IWindow; import android.view.IWindowSession; import android.view.KeyEvent; @@ -990,13 +991,21 @@ public final class Bridge implements ILayoutBridge { private static final class WindowSession implements IWindowSession { @SuppressWarnings("unused") - public int add(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3) + public int add(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3, + InputChannel outInputchannel) throws RemoteException { // pass for now. return 0; } @SuppressWarnings("unused") + public int addWithoutInputChannel(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3) + throws RemoteException { + // pass for now. + return 0; + } + + @SuppressWarnings("unused") public void finishDrawing(IWindow arg0) throws RemoteException { // pass for now. } |