From dc3eb4bf91d37ce6b8b000a59625a496b64b36fb Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Thu, 5 Mar 2015 18:21:06 -0800 Subject: [PATCH] Add support for non-blocking I/O with Looper. Bug: 10349083 Change-Id: I4a94b1eac53df57c05103913bd593d92b1e062d7 --- api/current.txt | 10 + api/system-current.txt | 10 + core/java/android/os/Looper.java | 1 + core/java/android/os/MessageQueue.java | 259 ++++++++++++++++++++++++- core/jni/android_os_MessageQueue.cpp | 92 +++++++-- core/jni/android_os_MessageQueue.h | 2 +- 6 files changed, 352 insertions(+), 22 deletions(-) diff --git a/api/current.txt b/api/current.txt index 1ef52c491708e..f42b32bf0650d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -22372,7 +22372,17 @@ package android.os { public final class MessageQueue { method public void addIdleHandler(android.os.MessageQueue.IdleHandler); method public boolean isIdle(); + method public void registerFileDescriptorCallback(java.io.FileDescriptor, int, android.os.MessageQueue.FileDescriptorCallback); method public void removeIdleHandler(android.os.MessageQueue.IdleHandler); + method public void unregisterFileDescriptorCallback(java.io.FileDescriptor); + } + + public static abstract class MessageQueue.FileDescriptorCallback { + ctor public MessageQueue.FileDescriptorCallback(); + method public int onFileDescriptorEvents(java.io.FileDescriptor, int); + field public static final int EVENT_ERROR = 4; // 0x4 + field public static final int EVENT_INPUT = 1; // 0x1 + field public static final int EVENT_OUTPUT = 2; // 0x2 } public static abstract interface MessageQueue.IdleHandler { diff --git a/api/system-current.txt b/api/system-current.txt index 55addb77501b5..ed13ab27eec6f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -23967,7 +23967,17 @@ package android.os { public final class MessageQueue { method public void addIdleHandler(android.os.MessageQueue.IdleHandler); method public boolean isIdle(); + method public void registerFileDescriptorCallback(java.io.FileDescriptor, int, android.os.MessageQueue.FileDescriptorCallback); method public void removeIdleHandler(android.os.MessageQueue.IdleHandler); + method public void unregisterFileDescriptorCallback(java.io.FileDescriptor); + } + + public static abstract class MessageQueue.FileDescriptorCallback { + ctor public MessageQueue.FileDescriptorCallback(); + method public int onFileDescriptorEvents(java.io.FileDescriptor, int); + field public static final int EVENT_ERROR = 4; // 0x4 + field public static final int EVENT_INPUT = 1; // 0x1 + field public static final int EVENT_OUTPUT = 2; // 0x2 } public static abstract interface MessageQueue.IdleHandler { diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index 9b3880b259b7b..34c880f60520a 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -273,6 +273,7 @@ public final class Looper { mQueue.dump(pw, prefix + " "); } + @Override public String toString() { return "Looper (" + mThread.getName() + ", tid " + mThread.getId() + ") {" + Integer.toHexString(System.identityHashCode(this)) + "}"; diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index d672f9bbf935b..7dd4f94f89344 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -16,9 +16,15 @@ package android.os; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.util.Log; import android.util.Printer; +import android.util.SparseArray; +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; /** @@ -30,6 +36,9 @@ import java.util.ArrayList; * {@link Looper#myQueue() Looper.myQueue()}. */ public final class MessageQueue { + private static final String TAG = "MessageQueue"; + private static final boolean DEBUG = false; + // True if the message queue can be quit. private final boolean mQuitAllowed; @@ -38,6 +47,7 @@ public final class MessageQueue { Message mMessages; private final ArrayList mIdleHandlers = new ArrayList(); + private SparseArray mFileDescriptorRecords; private IdleHandler[] mPendingIdleHandlers; private boolean mQuitting; @@ -50,9 +60,10 @@ public final class MessageQueue { private native static long nativeInit(); private native static void nativeDestroy(long ptr); - private native static void nativePollOnce(long ptr, int timeoutMillis); + private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ private native static void nativeWake(long ptr); private native static boolean nativeIsPolling(long ptr); + private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events); MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; @@ -101,7 +112,7 @@ public final class MessageQueue { * * @param handler The IdleHandler to be added. */ - public void addIdleHandler(IdleHandler handler) { + public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } @@ -119,7 +130,7 @@ public final class MessageQueue { * * @param handler The IdleHandler to be removed. */ - public void removeIdleHandler(IdleHandler handler) { + public void removeIdleHandler(@NonNull IdleHandler handler) { synchronized (this) { mIdleHandlers.remove(handler); } @@ -148,6 +159,151 @@ public final class MessageQueue { return !mQuitting && nativeIsPolling(mPtr); } + /** + * Registers a file descriptor callback to receive notification when file descriptor + * related events occur. + *

+ * If the file descriptor has already been registered, the specified events + * and callback will replace any that were previously associated with it. + * It is not possible to set more than one callback per file descriptor. + *

+ * It is important to always unregister the callback when the file descriptor + * is no longer of use. + *

+ * + * @param fd The file descriptor for which a callback will be registered. + * @param events The set of events to receive: a combination of the + * {@link FileDescriptorCallback#EVENT_INPUT}, + * {@link FileDescriptorCallback#EVENT_OUTPUT}, and + * {@link FileDescriptorCallback#EVENT_ERROR} event masks. If the requested + * set of events is zero, then the callback is unregistered. + * @param callback The callback to invoke when file descriptor events occur. + * + * @see FileDescriptorCallback + * @see #unregisterFileDescriptorCallback + */ + public void registerFileDescriptorCallback(@NonNull FileDescriptor fd, + @FileDescriptorCallback.Events int events, + @NonNull FileDescriptorCallback callback) { + if (fd == null) { + throw new IllegalArgumentException("fd must not be null"); + } + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + synchronized (this) { + setFileDescriptorCallbackLocked(fd, events, callback); + } + } + + /** + * Unregisters a file descriptor callback. + *

+ * This method does nothing if no callback has been registered for the + * specified file descriptor. + *

+ * + * @param fd The file descriptor whose callback will be unregistered. + * + * @see FileDescriptorCallback + * @see #registerFileDescriptorCallback + */ + public void unregisterFileDescriptorCallback(@NonNull FileDescriptor fd) { + if (fd == null) { + throw new IllegalArgumentException("fd must not be null"); + } + + synchronized (this) { + setFileDescriptorCallbackLocked(fd, 0, null); + } + } + + private void setFileDescriptorCallbackLocked(FileDescriptor fd, int events, + FileDescriptorCallback callback) { + final int fdNum = fd.getInt$(); + + int index = -1; + FileDescriptorRecord record = null; + if (mFileDescriptorRecords != null) { + index = mFileDescriptorRecords.indexOfKey(fdNum); + if (index >= 0) { + record = mFileDescriptorRecords.valueAt(index); + if (record != null && record.mEvents == events) { + return; + } + } + } + + if (events != 0) { + events |= FileDescriptorCallback.EVENT_ERROR; + if (record == null) { + if (mFileDescriptorRecords == null) { + mFileDescriptorRecords = new SparseArray(); + } + record = new FileDescriptorRecord(fd, events, callback); + mFileDescriptorRecords.put(fdNum, record); + } else { + record.mCallback = callback; + record.mEvents = events; + record.mSeq += 1; + } + nativeSetFileDescriptorEvents(mPtr, fdNum, events); + } else if (record != null) { + record.mEvents = 0; + mFileDescriptorRecords.removeAt(index); + } + } + + // Called from native code. + private int dispatchEvents(int fd, int events) { + // Get the file descriptor record and any state that might change. + final FileDescriptorRecord record; + final int oldWatchedEvents; + final FileDescriptorCallback callback; + final int seq; + synchronized (this) { + record = mFileDescriptorRecords.get(fd); + if (record == null) { + return 0; // spurious, no callback registered + } + + oldWatchedEvents = record.mEvents; + events &= oldWatchedEvents; // filter events based on current watched set + if (events == 0) { + return oldWatchedEvents; // spurious, watched events changed + } + + callback = record.mCallback; + seq = record.mSeq; + } + + // Invoke the callback outside of the lock. + int newWatchedEvents = callback.onFileDescriptorEvents( + record.mDescriptor, events); + if (newWatchedEvents != 0) { + newWatchedEvents |= FileDescriptorCallback.EVENT_ERROR; + } + + // Update the file descriptor record if the callback changed the set of + // events to watch and the callback itself hasn't been updated since. + if (newWatchedEvents != oldWatchedEvents) { + synchronized (this) { + int index = mFileDescriptorRecords.indexOfKey(fd); + if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record + && record.mSeq == seq) { + record.mEvents = newWatchedEvents; + if (newWatchedEvents == 0) { + mFileDescriptorRecords.removeAt(index); + } + } + } + } + + // Return the new set of events to watch for native code to take care of. + return newWatchedEvents; + } + Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit @@ -191,7 +347,8 @@ public final class MessageQueue { mMessages = msg.next; } msg.next = null; - if (false) Log.v("MessageQueue", "Returning message: " + msg); + if (DEBUG) Log.v(TAG, "Returning message: " + msg); + msg.markInUse(); return msg; } } else { @@ -234,7 +391,7 @@ public final class MessageQueue { try { keep = idler.queueIdle(); } catch (Throwable t) { - Log.wtf("MessageQueue", "IdleHandler threw exception", t); + Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { @@ -385,7 +542,7 @@ public final class MessageQueue { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); - Log.w("MessageQueue", e.getMessage(), e); + Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } @@ -627,4 +784,94 @@ public final class MessageQueue { */ boolean queueIdle(); } + + /** + * A callback which is invoked when file descriptor related events occur. + */ + public static abstract class FileDescriptorCallback { + /** + * File descriptor event: Indicates that the file descriptor is ready for input + * operations, such as reading. + *

+ * The callback should read all available data from the file descriptor + * then return true to keep the callback active or false + * to remove the callback. + *

+ * In the case of a socket, this event may be generated to indicate + * that there is at least one incoming connection that the callback + * should accept. + *

+ * This event will only be generated if the {@link #EVENT_INPUT} event mask was + * specified when the callback was added. + *

+ */ + public static final int EVENT_INPUT = 1 << 0; + + /** + * File descriptor event: Indicates that the file descriptor is ready for output + * operations, such as writing. + *

+ * The callback should write as much data as it needs. If it could not + * write everything at once, then it should return true to + * keep the callback active. Otherwise, it should return false + * to remove the callback then re-register it later when it needs to write + * something else. + *

+ * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was + * specified when the callback was added. + *

+ */ + public static final int EVENT_OUTPUT = 1 << 1; + + /** + * File descriptor event: Indicates that the file descriptor encountered a + * fatal error. + *

+ * File descriptor errors can occur for various reasons. One common error + * is when the remote peer of a socket or pipe closes its end of the connection. + *

+ * This event may be generated at any time regardless of whether the + * {@link #EVENT_ERROR} event mask was specified when the callback was added. + *

+ */ + public static final int EVENT_ERROR = 1 << 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag=true, value={EVENT_INPUT, EVENT_OUTPUT, EVENT_ERROR}) + public @interface Events {} + + /** + * Called when a file descriptor receives events. + *

+ * The default implementation does nothing and returns 0 to unregister the callback. + *

+ * + * @param fd The file descriptor. + * @param events The set of events that occurred: a combination of the + * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks. + * @return The new set of events to watch, or 0 to unregister the callback. + * + * @see #EVENT_INPUT + * @see #EVENT_OUTPUT + * @see #EVENT_ERROR + */ + public @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events) { + return 0; + } + } + + private static final class FileDescriptorRecord { + public final FileDescriptor mDescriptor; + public int mEvents; + public FileDescriptorCallback mCallback; + public int mSeq; + + public FileDescriptorRecord(FileDescriptor descriptor, + int events, FileDescriptorCallback callback) { + mDescriptor = descriptor; + mEvents = events; + mCallback = callback; + } + } } diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp index ee64044322626..854e6b7b0507e 100644 --- a/core/jni/android_os_MessageQueue.cpp +++ b/core/jni/android_os_MessageQueue.cpp @@ -29,22 +29,31 @@ namespace android { static struct { jfieldID mPtr; // native object attached to the DVM MessageQueue + jmethodID dispatchEvents; } gMessageQueueClassInfo; +// Must be kept in sync with the constants in Looper.FileDescriptorCallback +static const int CALLBACK_EVENT_INPUT = 1 << 0; +static const int CALLBACK_EVENT_OUTPUT = 1 << 1; +static const int CALLBACK_EVENT_ERROR = 1 << 2; -class NativeMessageQueue : public MessageQueue { + +class NativeMessageQueue : public MessageQueue, public LooperCallback { public: NativeMessageQueue(); virtual ~NativeMessageQueue(); virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj); - void pollOnce(JNIEnv* env, int timeoutMillis); - + void pollOnce(JNIEnv* env, jobject obj, int timeoutMillis); void wake(); + void setFileDescriptorEvents(int fd, int events); + + virtual int handleEvent(int fd, int events, void* data); private: - bool mInCallback; + JNIEnv* mPollEnv; + jobject mPollObj; jthrowable mExceptionObj; }; @@ -66,10 +75,11 @@ bool MessageQueue::raiseAndClearException(JNIEnv* env, const char* msg) { return false; } -NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) { +NativeMessageQueue::NativeMessageQueue() : + mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) { mLooper = Looper::getForThread(); if (mLooper == NULL) { - mLooper = new Looper(false); + mLooper = new Looper(true); Looper::setForThread(mLooper); } } @@ -79,7 +89,7 @@ NativeMessageQueue::~NativeMessageQueue() { void NativeMessageQueue::raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj) { if (exceptionObj) { - if (mInCallback) { + if (mPollEnv == env) { if (mExceptionObj) { env->DeleteLocalRef(mExceptionObj); } @@ -94,10 +104,13 @@ void NativeMessageQueue::raiseException(JNIEnv* env, const char* msg, jthrowable } } -void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) { - mInCallback = true; +void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) { + mPollEnv = env; + mPollObj = pollObj; mLooper->pollOnce(timeoutMillis); - mInCallback = false; + mPollObj = NULL; + mPollEnv = NULL; + if (mExceptionObj) { env->Throw(mExceptionObj); env->DeleteLocalRef(mExceptionObj); @@ -109,6 +122,46 @@ void NativeMessageQueue::wake() { mLooper->wake(); } +void NativeMessageQueue::setFileDescriptorEvents(int fd, int events) { + if (events) { + int looperEvents = 0; + if (events & CALLBACK_EVENT_INPUT) { + looperEvents |= Looper::EVENT_INPUT; + } + if (events & CALLBACK_EVENT_OUTPUT) { + looperEvents |= Looper::EVENT_OUTPUT; + } + mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this, + reinterpret_cast(events)); + } else { + mLooper->removeFd(fd); + } +} + +int NativeMessageQueue::handleEvent(int fd, int looperEvents, void* data) { + int events = 0; + if (looperEvents & Looper::EVENT_INPUT) { + events |= CALLBACK_EVENT_INPUT; + } + if (looperEvents & Looper::EVENT_OUTPUT) { + events |= CALLBACK_EVENT_OUTPUT; + } + if (looperEvents & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP | Looper::EVENT_INVALID)) { + events |= CALLBACK_EVENT_ERROR; + } + int oldWatchedEvents = reinterpret_cast(data); + int newWatchedEvents = mPollEnv->CallIntMethod(mPollObj, + gMessageQueueClassInfo.dispatchEvents, fd, events); + if (!newWatchedEvents) { + return 0; // unregister the fd + } + if (newWatchedEvents != oldWatchedEvents) { + setFileDescriptorEvents(fd, newWatchedEvents); + } + return 1; +} + + // ---------------------------------------------------------------------------- sp android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) { @@ -132,15 +185,15 @@ static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jclass clazz, jlo nativeMessageQueue->decStrong(env); } -static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz, +static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr); - nativeMessageQueue->pollOnce(env, timeoutMillis); + nativeMessageQueue->pollOnce(env, obj, timeoutMillis); } static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr); - return nativeMessageQueue->wake(); + nativeMessageQueue->wake(); } static jboolean android_os_MessageQueue_nativeIsPolling(JNIEnv* env, jclass clazz, jlong ptr) { @@ -148,6 +201,12 @@ static jboolean android_os_MessageQueue_nativeIsPolling(JNIEnv* env, jclass claz return nativeMessageQueue->getLooper()->isPolling(); } +static void android_os_MessageQueue_nativeSetFileDescriptorEvents(JNIEnv* env, jclass clazz, + jlong ptr, jint fd, jint events) { + NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr); + nativeMessageQueue->setFileDescriptorEvents(fd, events); +} + // ---------------------------------------------------------------------------- static JNINativeMethod gMessageQueueMethods[] = { @@ -156,7 +215,9 @@ static JNINativeMethod gMessageQueueMethods[] = { { "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy }, { "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce }, { "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake }, - { "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling } + { "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling }, + { "nativeSetFileDescriptorEvents", "(JII)V", + (void*)android_os_MessageQueue_nativeSetFileDescriptorEvents }, }; int register_android_os_MessageQueue(JNIEnv* env) { @@ -164,8 +225,9 @@ int register_android_os_MessageQueue(JNIEnv* env) { NELEM(gMessageQueueMethods)); jclass clazz = FindClassOrDie(env, "android/os/MessageQueue"); - gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J"); + gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz, + "dispatchEvents", "(II)I"); return res; } diff --git a/core/jni/android_os_MessageQueue.h b/core/jni/android_os_MessageQueue.h index 49d2aa0c283d7..1e49b5f3b079b 100644 --- a/core/jni/android_os_MessageQueue.h +++ b/core/jni/android_os_MessageQueue.h @@ -22,7 +22,7 @@ namespace android { -class MessageQueue : public RefBase { +class MessageQueue : public virtual RefBase { public: /* Gets the message queue's looper. */ inline sp getLooper() const {