diff options
Diffstat (limited to 'core')
28 files changed, 2319 insertions, 442 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) { |