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 <nativehelper/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    explicit 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        std::unique_ptr<NativeInputChannel> nativeInputChannel) {
115    jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
116            gInputChannelClassInfo.ctor);
117    if (inputChannelObj) {
118        android_view_InputChannel_setNativeInputChannel(env, inputChannelObj,
119                 nativeInputChannel.release());
120    }
121    return inputChannelObj;
122}
123
124static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
125        jclass clazz, jstring nameObj) {
126    const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
127    std::string name = nameChars;
128    env->ReleaseStringUTFChars(nameObj, nameChars);
129
130    sp<InputChannel> serverChannel;
131    sp<InputChannel> clientChannel;
132    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
133
134    if (result) {
135        String8 message;
136        message.appendFormat("Could not open input channel pair.  status=%d", result);
137        jniThrowRuntimeException(env, message.string());
138        return NULL;
139    }
140
141    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
142    if (env->ExceptionCheck()) {
143        return NULL;
144    }
145
146    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
147            std::make_unique<NativeInputChannel>(serverChannel));
148    if (env->ExceptionCheck()) {
149        return NULL;
150    }
151
152    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
153            std::make_unique<NativeInputChannel>(clientChannel));
154    if (env->ExceptionCheck()) {
155        return NULL;
156    }
157
158    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
159    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
160    return channelPair;
161}
162
163static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jboolean finalized) {
164    NativeInputChannel* nativeInputChannel =
165            android_view_InputChannel_getNativeInputChannel(env, obj);
166    if (nativeInputChannel) {
167        if (finalized) {
168            ALOGW("Input channel object '%s' was finalized without being disposed!",
169                    nativeInputChannel->getInputChannel()->getName().c_str());
170        }
171
172        nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);
173
174        android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
175        delete nativeInputChannel;
176    }
177}
178
179static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,
180        jobject otherObj) {
181    if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
182        jniThrowException(env, "java/lang/IllegalStateException",
183                "Other object already has a native input channel.");
184        return;
185    }
186
187    NativeInputChannel* nativeInputChannel =
188            android_view_InputChannel_getNativeInputChannel(env, obj);
189    android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);
190    android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
191}
192
193static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj,
194        jobject parcelObj) {
195    if (android_view_InputChannel_getNativeInputChannel(env, obj) != NULL) {
196        jniThrowException(env, "java/lang/IllegalStateException",
197                "This object already has a native input channel.");
198        return;
199    }
200
201    Parcel* parcel = parcelForJavaObject(env, parcelObj);
202    if (parcel) {
203        bool isInitialized = parcel->readInt32();
204        if (isInitialized) {
205            String8 name = parcel->readString8();
206            int rawFd = parcel->readFileDescriptor();
207            int dupFd = dup(rawFd);
208            if (dupFd < 0) {
209                ALOGE("Error %d dup channel fd %d.", errno, rawFd);
210                jniThrowRuntimeException(env,
211                        "Could not read input channel file descriptors from parcel.");
212                return;
213            }
214
215            InputChannel* inputChannel = new InputChannel(name.string(), dupFd);
216            NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel);
217
218            android_view_InputChannel_setNativeInputChannel(env, obj, nativeInputChannel);
219        }
220    }
221}
222
223static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject obj,
224        jobject parcelObj) {
225    Parcel* parcel = parcelForJavaObject(env, parcelObj);
226    if (parcel) {
227        NativeInputChannel* nativeInputChannel =
228                android_view_InputChannel_getNativeInputChannel(env, obj);
229        if (nativeInputChannel) {
230            sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel();
231
232            parcel->writeInt32(1);
233            parcel->writeString8(String8(inputChannel->getName().c_str()));
234            parcel->writeDupFileDescriptor(inputChannel->getFd());
235        } else {
236            parcel->writeInt32(0);
237        }
238    }
239}
240
241static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) {
242    NativeInputChannel* nativeInputChannel =
243            android_view_InputChannel_getNativeInputChannel(env, obj);
244    if (! nativeInputChannel) {
245        return NULL;
246    }
247
248    jstring name = env->NewStringUTF(nativeInputChannel->getInputChannel()->getName().c_str());
249    return name;
250}
251
252static void android_view_InputChannel_nativeDup(JNIEnv* env, jobject obj, jobject otherObj) {
253    NativeInputChannel* nativeInputChannel =
254            android_view_InputChannel_getNativeInputChannel(env, obj);
255    if (nativeInputChannel) {
256        android_view_InputChannel_setNativeInputChannel(env, otherObj,
257                new NativeInputChannel(nativeInputChannel->getInputChannel()->dup()));
258    }
259}
260
261// ----------------------------------------------------------------------------
262
263static const JNINativeMethod gInputChannelMethods[] = {
264    /* name, signature, funcPtr */
265    { "nativeOpenInputChannelPair", "(Ljava/lang/String;)[Landroid/view/InputChannel;",
266            (void*)android_view_InputChannel_nativeOpenInputChannelPair },
267    { "nativeDispose", "(Z)V",
268            (void*)android_view_InputChannel_nativeDispose },
269    { "nativeTransferTo", "(Landroid/view/InputChannel;)V",
270            (void*)android_view_InputChannel_nativeTransferTo },
271    { "nativeReadFromParcel", "(Landroid/os/Parcel;)V",
272            (void*)android_view_InputChannel_nativeReadFromParcel },
273    { "nativeWriteToParcel", "(Landroid/os/Parcel;)V",
274            (void*)android_view_InputChannel_nativeWriteToParcel },
275    { "nativeGetName", "()Ljava/lang/String;",
276            (void*)android_view_InputChannel_nativeGetName },
277    { "nativeDup", "(Landroid/view/InputChannel;)V",
278            (void*)android_view_InputChannel_nativeDup },
279};
280
281int register_android_view_InputChannel(JNIEnv* env) {
282    int res = RegisterMethodsOrDie(env, "android/view/InputChannel", gInputChannelMethods,
283                                   NELEM(gInputChannelMethods));
284
285    jclass clazz = FindClassOrDie(env, "android/view/InputChannel");
286    gInputChannelClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
287
288    gInputChannelClassInfo.mPtr = GetFieldIDOrDie(env, gInputChannelClassInfo.clazz, "mPtr", "J");
289
290    gInputChannelClassInfo.ctor = GetMethodIDOrDie(env, gInputChannelClassInfo.clazz, "<init>",
291                                                   "()V");
292
293    return res;
294}
295
296} // namespace android
297