1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "InputChannel-JNI"
18
19#include "JNIHelp.h"
20
21#include <android_runtime/AndroidRuntime.h>
22#include <binder/Parcel.h>
23#include <utils/Log.h>
24#include <input/InputTransport.h>
25#include "android_view_InputChannel.h"
26#include "android_os_Parcel.h"
27#include "android_util_Binder.h"
28
29#include "core_jni_helpers.h"
30
31namespace android {
32
33// ----------------------------------------------------------------------------
34
35static struct {
36    jclass clazz;
37
38    jfieldID mPtr;   // native object attached to the DVM InputChannel
39    jmethodID ctor;
40} gInputChannelClassInfo;
41
42// ----------------------------------------------------------------------------
43
44class NativeInputChannel {
45public:
46    NativeInputChannel(const sp<InputChannel>& inputChannel);
47    ~NativeInputChannel();
48
49    inline sp<InputChannel> getInputChannel() { return mInputChannel; }
50
51    void setDisposeCallback(InputChannelObjDisposeCallback callback, void* data);
52    void invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj);
53
54private:
55    sp<InputChannel> mInputChannel;
56    InputChannelObjDisposeCallback mDisposeCallback;
57    void* mDisposeData;
58};
59
60// ----------------------------------------------------------------------------
61
62NativeInputChannel::NativeInputChannel(const sp<InputChannel>& inputChannel) :
63    mInputChannel(inputChannel), mDisposeCallback(NULL) {
64}
65
66NativeInputChannel::~NativeInputChannel() {
67}
68
69void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) {
70    mDisposeCallback = callback;
71    mDisposeData = data;
72}
73
74void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) {
75    if (mDisposeCallback) {
76        mDisposeCallback(env, obj, mInputChannel, mDisposeData);
77        mDisposeCallback = NULL;
78        mDisposeData = NULL;
79    }
80}
81
82// ----------------------------------------------------------------------------
83
84static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env,
85        jobject inputChannelObj) {
86    jlong longPtr = env->GetLongField(inputChannelObj, gInputChannelClassInfo.mPtr);
87    return reinterpret_cast<NativeInputChannel*>(longPtr);
88}
89
90static void android_view_InputChannel_setNativeInputChannel(JNIEnv* env, jobject inputChannelObj,
91        NativeInputChannel* nativeInputChannel) {
92    env->SetLongField(inputChannelObj, gInputChannelClassInfo.mPtr,
93             reinterpret_cast<jlong>(nativeInputChannel));
94}
95
96sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj) {
97    NativeInputChannel* nativeInputChannel =
98            android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
99    return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL;
100}
101
102void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj,
103        InputChannelObjDisposeCallback callback, void* data) {
104    NativeInputChannel* nativeInputChannel =
105            android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
106    if (nativeInputChannel == NULL) {
107        ALOGW("Cannot set dispose callback because input channel object has not been initialized.");
108    } else {
109        nativeInputChannel->setDisposeCallback(callback, data);
110    }
111}
112
113static jobject android_view_InputChannel_createInputChannel(JNIEnv* env,
114        NativeInputChannel* nativeInputChannel) {
115    jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
116            gInputChannelClassInfo.ctor);
117    if (inputChannelObj) {
118        android_view_InputChannel_setNativeInputChannel(env, inputChannelObj, nativeInputChannel);
119    }
120    return inputChannelObj;
121}
122
123static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
124        jclass clazz, jstring nameObj) {
125    const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
126    String8 name(nameChars);
127    env->ReleaseStringUTFChars(nameObj, nameChars);
128
129    sp<InputChannel> serverChannel;
130    sp<InputChannel> clientChannel;
131    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
132
133    if (result) {
134        String8 message;
135        message.appendFormat("Could not open input channel pair.  status=%d", result);
136        jniThrowRuntimeException(env, message.string());
137        return NULL;
138    }
139
140    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
141    if (env->ExceptionCheck()) {
142        return NULL;
143    }
144
145    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
146            new NativeInputChannel(serverChannel));
147    if (env->ExceptionCheck()) {
148        return NULL;
149    }
150
151    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
152            new NativeInputChannel(clientChannel));
153    if (env->ExceptionCheck()) {
154        return NULL;
155    }
156
157    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
158    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
159    return channelPair;
160}
161
162static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jboolean finalized) {
163    NativeInputChannel* nativeInputChannel =
164            android_view_InputChannel_getNativeInputChannel(env, obj);
165    if (nativeInputChannel) {
166        if (finalized) {
167            ALOGW("Input channel object '%s' was finalized without being disposed!",
168                    nativeInputChannel->getInputChannel()->getName().string());
169        }
170
171        nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);
172
173        android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
174        delete nativeInputChannel;
175    }
176}
177
178static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,
179        jobject otherObj) {
180    if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
181        jniThrowException(env, "java/lang/IllegalStateException",
182                "Other object already has a native input channel.");
183        return;
184    }
185
186    NativeInputChannel* nativeInputChannel =
187            android_view_InputChannel_getNativeInputChannel(env, obj);
188    android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);
189    android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
190}
191
192static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj,
193        jobject parcelObj) {
194    if (android_view_InputChannel_getNativeInputChannel(env, obj) != NULL) {
195        jniThrowException(env, "java/lang/IllegalStateException",
196                "This object already has a native input channel.");
197        return;
198    }
199
200    Parcel* parcel = parcelForJavaObject(env, parcelObj);
201    if (parcel) {
202        bool isInitialized = parcel->readInt32();
203        if (isInitialized) {
204            String8 name = parcel->readString8();
205            int rawFd = parcel->readFileDescriptor();
206            int dupFd = dup(rawFd);
207            if (dupFd < 0) {
208                ALOGE("Error %d dup channel fd %d.", errno, rawFd);
209                jniThrowRuntimeException(env,
210                        "Could not read input channel file descriptors from parcel.");
211                return;
212            }
213
214            InputChannel* inputChannel = new InputChannel(name, dupFd);
215            NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel);
216
217            android_view_InputChannel_setNativeInputChannel(env, obj, nativeInputChannel);
218        }
219    }
220}
221
222static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject obj,
223        jobject parcelObj) {
224    Parcel* parcel = parcelForJavaObject(env, parcelObj);
225    if (parcel) {
226        NativeInputChannel* nativeInputChannel =
227                android_view_InputChannel_getNativeInputChannel(env, obj);
228        if (nativeInputChannel) {
229            sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel();
230
231            parcel->writeInt32(1);
232            parcel->writeString8(inputChannel->getName());
233            parcel->writeDupFileDescriptor(inputChannel->getFd());
234        } else {
235            parcel->writeInt32(0);
236        }
237    }
238}
239
240static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) {
241    NativeInputChannel* nativeInputChannel =
242            android_view_InputChannel_getNativeInputChannel(env, obj);
243    if (! nativeInputChannel) {
244        return NULL;
245    }
246
247    jstring name = env->NewStringUTF(nativeInputChannel->getInputChannel()->getName().string());
248    return name;
249}
250
251static void android_view_InputChannel_nativeDup(JNIEnv* env, jobject obj, jobject otherObj) {
252    NativeInputChannel* nativeInputChannel =
253            android_view_InputChannel_getNativeInputChannel(env, obj);
254    if (nativeInputChannel) {
255        android_view_InputChannel_setNativeInputChannel(env, otherObj,
256                new NativeInputChannel(nativeInputChannel->getInputChannel()->dup()));
257    }
258}
259
260// ----------------------------------------------------------------------------
261
262static const JNINativeMethod gInputChannelMethods[] = {
263    /* name, signature, funcPtr */
264    { "nativeOpenInputChannelPair", "(Ljava/lang/String;)[Landroid/view/InputChannel;",
265            (void*)android_view_InputChannel_nativeOpenInputChannelPair },
266    { "nativeDispose", "(Z)V",
267            (void*)android_view_InputChannel_nativeDispose },
268    { "nativeTransferTo", "(Landroid/view/InputChannel;)V",
269            (void*)android_view_InputChannel_nativeTransferTo },
270    { "nativeReadFromParcel", "(Landroid/os/Parcel;)V",
271            (void*)android_view_InputChannel_nativeReadFromParcel },
272    { "nativeWriteToParcel", "(Landroid/os/Parcel;)V",
273            (void*)android_view_InputChannel_nativeWriteToParcel },
274    { "nativeGetName", "()Ljava/lang/String;",
275            (void*)android_view_InputChannel_nativeGetName },
276    { "nativeDup", "(Landroid/view/InputChannel;)V",
277            (void*)android_view_InputChannel_nativeDup },
278};
279
280int register_android_view_InputChannel(JNIEnv* env) {
281    int res = RegisterMethodsOrDie(env, "android/view/InputChannel", gInputChannelMethods,
282                                   NELEM(gInputChannelMethods));
283
284    jclass clazz = FindClassOrDie(env, "android/view/InputChannel");
285    gInputChannelClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
286
287    gInputChannelClassInfo.mPtr = GetFieldIDOrDie(env, gInputChannelClassInfo.clazz, "mPtr", "J");
288
289    gInputChannelClassInfo.ctor = GetMethodIDOrDie(env, gInputChannelClassInfo.clazz, "<init>",
290                                                   "()V");
291
292    return res;
293}
294
295} // namespace android
296