Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dc3eb4bf authored by Jeff Brown's avatar Jeff Brown
Browse files

Add support for non-blocking I/O with Looper.

Bug: 10349083
Change-Id: I4a94b1eac53df57c05103913bd593d92b1e062d7
parent 803c2aff
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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 {
+10 −0
Original line number Diff line number Diff line
@@ -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 {
+1 −0
Original line number Diff line number Diff line
@@ -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)) + "}";
+253 −6
Original line number Diff line number Diff line
@@ -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<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    private SparseArray<FileDescriptorRecord> 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.
     * <p>
     * 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.
     * </p><p>
     * It is important to always unregister the callback when the file descriptor
     * is no longer of use.
     * </p>
     *
     * @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.
     * <p>
     * This method does nothing if no callback has been registered for the
     * specified file descriptor.
     * </p>
     *
     * @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<FileDescriptorRecord>();
                }
                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.
         * <p>
         * The callback should read all available data from the file descriptor
         * then return <code>true</code> to keep the callback active or <code>false</code>
         * to remove the callback.
         * </p><p>
         * 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.
         * </p><p>
         * This event will only be generated if the {@link #EVENT_INPUT} event mask was
         * specified when the callback was added.
         * </p>
         */
        public static final int EVENT_INPUT = 1 << 0;

        /**
         * File descriptor event: Indicates that the file descriptor is ready for output
         * operations, such as writing.
         * <p>
         * The callback should write as much data as it needs.  If it could not
         * write everything at once, then it should return <code>true</code> to
         * keep the callback active.  Otherwise, it should return <code>false</code>
         * to remove the callback then re-register it later when it needs to write
         * something else.
         * </p><p>
         * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
         * specified when the callback was added.
         * </p>
         */
        public static final int EVENT_OUTPUT = 1 << 1;

        /**
         * File descriptor event: Indicates that the file descriptor encountered a
         * fatal error.
         * <p>
         * 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.
         * </p><p>
         * This event may be generated at any time regardless of whether the
         * {@link #EVENT_ERROR} event mask was specified when the callback was added.
         * </p>
         */
        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.
         * <p>
         * The default implementation does nothing and returns 0 to unregister the callback.
         * </p>
         *
         * @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;
        }
    }
}
+77 −15
Original line number Diff line number Diff line
@@ -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<void*>(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<int>(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<MessageQueue> 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<NativeMessageQueue*>(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<NativeMessageQueue*>(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<NativeMessageQueue*>(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;
}
Loading