Fix reference cycle in InputEventReceiver and Sender.

If the receiver or sender was not properly disposed, then
the underlying input channel might be leaked because the
native peer was holding a strong reference to the object.

Switched to using a weak reference.

Also updated Binder to use a new helper created for this purpose.

Change-Id: I19680bf96d0548777bff02aa1d91874d1e8e41da
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 117c101..f5ee7ed 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -23,6 +23,8 @@
 import android.util.Log;
 import android.util.SparseIntArray;
 
+import java.lang.ref.WeakReference;
+
 /**
  * Provides a low-level mechanism for an application to receive input events.
  * @hide
@@ -42,7 +44,7 @@
     // Map from InputEvent sequence numbers to dispatcher sequence numbers.
     private final SparseIntArray mSeqMap = new SparseIntArray();
 
-    private static native int nativeInit(InputEventReceiver receiver,
+    private static native int nativeInit(WeakReference<InputEventReceiver> receiver,
             InputChannel inputChannel, MessageQueue messageQueue);
     private static native void nativeDispose(int receiverPtr);
     private static native void nativeFinishInputEvent(int receiverPtr, int seq, boolean handled);
@@ -65,7 +67,8 @@
 
         mInputChannel = inputChannel;
         mMessageQueue = looper.getQueue();
-        mReceiverPtr = nativeInit(this, inputChannel, mMessageQueue);
+        mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
+                inputChannel, mMessageQueue);
 
         mCloseGuard.open("dispose");
     }
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
index adf63fe..be6a623 100644
--- a/core/java/android/view/InputEventSender.java
+++ b/core/java/android/view/InputEventSender.java
@@ -22,6 +22,8 @@
 import android.os.MessageQueue;
 import android.util.Log;
 
+import java.lang.ref.WeakReference;
+
 /**
  * Provides a low-level mechanism for an application to send input events.
  * @hide
@@ -38,7 +40,7 @@
     private InputChannel mInputChannel;
     private MessageQueue mMessageQueue;
 
-    private static native int nativeInit(InputEventSender sender,
+    private static native int nativeInit(WeakReference<InputEventSender> sender,
             InputChannel inputChannel, MessageQueue messageQueue);
     private static native void nativeDispose(int senderPtr);
     private static native boolean nativeSendKeyEvent(int senderPtr, int seq, KeyEvent event);
@@ -60,7 +62,8 @@
 
         mInputChannel = inputChannel;
         mMessageQueue = looper.getQueue();
-        mSenderPtr = nativeInit(this, inputChannel, mMessageQueue);
+        mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this),
+                inputChannel, mMessageQueue);
 
         mCloseGuard.open("dispose");
     }
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index a7eede2..8766cf9 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -93,14 +93,6 @@
 
 // ----------------------------------------------------------------------------
 
-static struct weakreference_offsets_t
-{
-    // Class state.
-    jclass mClass;
-    jmethodID mGet;
-
-} gWeakReferenceOffsets;
-
 static struct error_offsets_t
 {
     jclass mClass;
@@ -570,7 +562,7 @@
     // Someone else's...  do we know about it?
     jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
     if (object != NULL) {
-        jobject res = env->CallObjectMethod(object, gWeakReferenceOffsets.mGet);
+        jobject res = jniGetReferent(env, object);
         if (res != NULL) {
             ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res);
             return res;
@@ -1211,13 +1203,6 @@
 {
     jclass clazz;
 
-    clazz = env->FindClass("java/lang/ref/WeakReference");
-    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.lang.ref.WeakReference");
-    gWeakReferenceOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
-    gWeakReferenceOffsets.mGet
-        = env->GetMethodID(clazz, "get", "()Ljava/lang/Object;");
-    assert(gWeakReferenceOffsets.mGet);
-
     clazz = env->FindClass("java/lang/Error");
     LOG_FATAL_IF(clazz == NULL, "Unable to find class java.lang.Error");
     gErrorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 198814a..c350521 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -34,6 +34,8 @@
 #include "android_view_KeyEvent.h"
 #include "android_view_MotionEvent.h"
 
+#include <ScopedLocalRef.h>
+
 namespace android {
 
 static struct {
@@ -47,7 +49,7 @@
 class NativeInputEventReceiver : public LooperCallback {
 public:
     NativeInputEventReceiver(JNIEnv* env,
-            jobject receiverObj, const sp<InputChannel>& inputChannel,
+            jobject receiverWeak, const sp<InputChannel>& inputChannel,
             const sp<MessageQueue>& messageQueue);
 
     status_t initialize();
@@ -59,7 +61,7 @@
     virtual ~NativeInputEventReceiver();
 
 private:
-    jobject mReceiverObjGlobal;
+    jobject mReceiverWeakGlobal;
     InputConsumer mInputConsumer;
     sp<MessageQueue> mMessageQueue;
     PreallocatedInputEventFactory mInputEventFactory;
@@ -74,9 +76,9 @@
 
 
 NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env,
-        jobject receiverObj, const sp<InputChannel>& inputChannel,
+        jobject receiverWeak, const sp<InputChannel>& inputChannel,
         const sp<MessageQueue>& messageQueue) :
-        mReceiverObjGlobal(env->NewGlobalRef(receiverObj)),
+        mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
         mInputConsumer(inputChannel), mMessageQueue(messageQueue),
         mBatchedInputEventPending(false) {
 #if DEBUG_DISPATCH_CYCLE
@@ -86,7 +88,7 @@
 
 NativeInputEventReceiver::~NativeInputEventReceiver() {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    env->DeleteGlobalRef(mReceiverObjGlobal);
+    env->DeleteGlobalRef(mReceiverWeakGlobal);
 }
 
 status_t NativeInputEventReceiver::initialize() {
@@ -151,6 +153,7 @@
         mBatchedInputEventPending = false;
     }
 
+    ScopedLocalRef<jobject> receiverObj(env, NULL);
     bool skipCallbacks = false;
     for (;;) {
         uint32_t seq;
@@ -162,12 +165,21 @@
                 if (!skipCallbacks && !mBatchedInputEventPending
                         && mInputConsumer.hasPendingBatch()) {
                     // There is a pending batch.  Come back later.
+                    if (!receiverObj.get()) {
+                        receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
+                        if (!receiverObj.get()) {
+                            ALOGW("channel '%s' ~ Receiver object was finalized "
+                                    "without being disposed.", getInputChannelName());
+                            return DEAD_OBJECT;
+                        }
+                    }
+
                     mBatchedInputEventPending = true;
 #if DEBUG_DISPATCH_CYCLE
                     ALOGD("channel '%s' ~ Dispatching batched input event pending notification.",
                             getInputChannelName());
 #endif
-                    env->CallVoidMethod(mReceiverObjGlobal,
+                    env->CallVoidMethod(receiverObj.get(),
                             gInputEventReceiverClassInfo.dispatchBatchedInputEventPending);
                     if (env->ExceptionCheck()) {
                         ALOGE("Exception dispatching batched input events.");
@@ -183,6 +195,15 @@
         assert(inputEvent);
 
         if (!skipCallbacks) {
+            if (!receiverObj.get()) {
+                receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
+                if (!receiverObj.get()) {
+                    ALOGW("channel '%s' ~ Receiver object was finalized "
+                            "without being disposed.", getInputChannelName());
+                    return DEAD_OBJECT;
+                }
+            }
+
             jobject inputEventObj;
             switch (inputEvent->getType()) {
             case AINPUT_EVENT_TYPE_KEY:
@@ -210,12 +231,13 @@
 #if DEBUG_DISPATCH_CYCLE
                 ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName());
 #endif
-                env->CallVoidMethod(mReceiverObjGlobal,
+                env->CallVoidMethod(receiverObj.get(),
                         gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
                 if (env->ExceptionCheck()) {
                     ALOGE("Exception dispatching input event.");
                     skipCallbacks = true;
                 }
+                env->DeleteLocalRef(inputEventObj);
             } else {
                 ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName());
                 skipCallbacks = true;
@@ -229,7 +251,7 @@
 }
 
 
-static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj,
+static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
         jobject inputChannelObj, jobject messageQueueObj) {
     sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
             inputChannelObj);
@@ -245,7 +267,7 @@
     }
 
     sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
-            receiverObj, inputChannel, messageQueue);
+            receiverWeak, inputChannel, messageQueue);
     status_t status = receiver->initialize();
     if (status) {
         String8 message;
@@ -293,7 +315,7 @@
 static JNINativeMethod gMethods[] = {
     /* name, signature, funcPtr */
     { "nativeInit",
-            "(Landroid/view/InputEventReceiver;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I",
+            "(Ljava/lang/ref/WeakReference;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I",
             (void*)nativeInit },
     { "nativeDispose", "(I)V",
             (void*)nativeDispose },
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 8a149d8..b46eb4b 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -35,6 +35,8 @@
 #include "android_view_KeyEvent.h"
 #include "android_view_MotionEvent.h"
 
+#include <ScopedLocalRef.h>
+
 namespace android {
 
 static struct {
@@ -47,7 +49,7 @@
 class NativeInputEventSender : public LooperCallback {
 public:
     NativeInputEventSender(JNIEnv* env,
-            jobject senderObj, const sp<InputChannel>& inputChannel,
+            jobject senderWeak, const sp<InputChannel>& inputChannel,
             const sp<MessageQueue>& messageQueue);
 
     status_t initialize();
@@ -59,7 +61,7 @@
     virtual ~NativeInputEventSender();
 
 private:
-    jobject mSenderObjGlobal;
+    jobject mSenderWeakGlobal;
     InputPublisher mInputPublisher;
     sp<MessageQueue> mMessageQueue;
     KeyedVector<uint32_t, uint32_t> mPublishedSeqMap;
@@ -75,9 +77,9 @@
 
 
 NativeInputEventSender::NativeInputEventSender(JNIEnv* env,
-        jobject senderObj, const sp<InputChannel>& inputChannel,
+        jobject senderWeak, const sp<InputChannel>& inputChannel,
         const sp<MessageQueue>& messageQueue) :
-        mSenderObjGlobal(env->NewGlobalRef(senderObj)),
+        mSenderWeakGlobal(env->NewGlobalRef(senderWeak)),
         mInputPublisher(inputChannel), mMessageQueue(messageQueue),
         mNextPublishedSeq(1) {
 #if DEBUG_DISPATCH_CYCLE
@@ -87,7 +89,7 @@
 
 NativeInputEventSender::~NativeInputEventSender() {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    env->DeleteGlobalRef(mSenderObjGlobal);
+    env->DeleteGlobalRef(mSenderWeakGlobal);
 }
 
 status_t NativeInputEventSender::initialize() {
@@ -178,6 +180,7 @@
     ALOGD("channel '%s' ~ Receiving finished signals.", getInputChannelName());
 #endif
 
+    ScopedLocalRef<jobject> senderObj(env, NULL);
     bool skipCallbacks = false;
     for (;;) {
         uint32_t publishedSeq;
@@ -205,7 +208,16 @@
 #endif
 
             if (!skipCallbacks) {
-                env->CallVoidMethod(mSenderObjGlobal,
+                if (!senderObj.get()) {
+                    senderObj.reset(jniGetReferent(env, mSenderWeakGlobal));
+                    if (!senderObj.get()) {
+                        ALOGW("channel '%s' ~ Sender object was finalized "
+                                "without being disposed.", getInputChannelName());
+                        return DEAD_OBJECT;
+                    }
+                }
+
+                env->CallVoidMethod(senderObj.get(),
                         gInputEventSenderClassInfo.dispatchInputEventFinished,
                         jint(seq), jboolean(handled));
                 if (env->ExceptionCheck()) {
@@ -218,7 +230,7 @@
 }
 
 
-static jint nativeInit(JNIEnv* env, jclass clazz, jobject senderObj,
+static jint nativeInit(JNIEnv* env, jclass clazz, jobject senderWeak,
         jobject inputChannelObj, jobject messageQueueObj) {
     sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
             inputChannelObj);
@@ -234,7 +246,7 @@
     }
 
     sp<NativeInputEventSender> sender = new NativeInputEventSender(env,
-            senderObj, inputChannel, messageQueue);
+            senderWeak, inputChannel, messageQueue);
     status_t status = sender->initialize();
     if (status) {
         String8 message;
@@ -277,7 +289,7 @@
 static JNINativeMethod gMethods[] = {
     /* name, signature, funcPtr */
     { "nativeInit",
-            "(Landroid/view/InputEventSender;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I",
+            "(Ljava/lang/ref/WeakReference;Landroid/view/InputChannel;Landroid/os/MessageQueue;)I",
             (void*)nativeInit },
     { "nativeDispose", "(I)V",
             (void*)nativeDispose },