android_app_NativeActivity.cpp revision 289b9b62372ef52a06113b83dfb870e2c2fb325a
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 "NativeActivity"
18#include <utils/Log.h>
19
20#include <poll.h>
21#include <dlfcn.h>
22
23#include <android_runtime/AndroidRuntime.h>
24#include <android_runtime/android_view_Surface.h>
25#include <android_runtime/android_app_NativeActivity.h>
26#include <surfaceflinger/Surface.h>
27#include <ui/egl/android_natives.h>
28#include <ui/InputTransport.h>
29#include <utils/PollLoop.h>
30
31#include "JNIHelp.h"
32#include "android_os_MessageQueue.h"
33#include "android_view_InputChannel.h"
34#include "android_view_KeyEvent.h"
35
36namespace android
37{
38
39static struct {
40    jclass clazz;
41
42    jmethodID dispatchUnhandledKeyEvent;
43    jmethodID setWindowFlags;
44    jmethodID setWindowFormat;
45} gNativeActivityClassInfo;
46
47// ------------------------------------------------------------------------
48
49struct ActivityWork {
50    int32_t cmd;
51    int32_t arg1;
52    int32_t arg2;
53};
54
55enum {
56    CMD_DEF_KEY = 1,
57    CMD_SET_WINDOW_FORMAT,
58    CMD_SET_WINDOW_FLAGS,
59};
60
61static void write_work(int fd, int32_t cmd, int32_t arg1=0, int32_t arg2=0) {
62    ActivityWork work;
63    work.cmd = cmd;
64    work.arg1 = arg1;
65    work.arg2 = arg2;
66
67restart:
68    int res = write(fd, &work, sizeof(work));
69    if (res < 0 && errno == EINTR) {
70        goto restart;
71    }
72
73    if (res == sizeof(work)) return;
74
75    if (res < 0) LOGW("Failed writing to work fd: %s", strerror(errno));
76    else LOGW("Truncated writing to work fd: %d", res);
77}
78
79static bool read_work(int fd, ActivityWork* outWork) {
80    int res = read(fd, outWork, sizeof(ActivityWork));
81    // no need to worry about EINTR, poll loop will just come back again.
82    if (res == sizeof(ActivityWork)) return true;
83
84    if (res < 0) LOGW("Failed reading work fd: %s", strerror(errno));
85    else LOGW("Truncated reading work fd: %d", res);
86    return false;
87}
88
89// ------------------------------------------------------------------------
90
91/*
92 * Specialized input queue that allows unhandled key events to be dispatched
93 * back to the native activity's Java framework code.
94 */
95struct MyInputQueue : AInputQueue {
96    explicit MyInputQueue(const android::sp<android::InputChannel>& channel, int workWrite)
97        : AInputQueue(channel), mWorkWrite(workWrite) {
98    }
99
100    virtual void doDefaultKey(android::KeyEvent* keyEvent) {
101        mLock.lock();
102        LOGI("Default key: pending=%d write=%d\n", mPendingKeys.size(), mWorkWrite);
103        if (mPendingKeys.size() <= 0 && mWorkWrite >= 0) {
104            write_work(mWorkWrite, CMD_DEF_KEY);
105        }
106        mPendingKeys.add(keyEvent);
107        mLock.unlock();
108    }
109
110    KeyEvent* getNextEvent() {
111        KeyEvent* event = NULL;
112
113        mLock.lock();
114        if (mPendingKeys.size() > 0) {
115            event = mPendingKeys[0];
116            mPendingKeys.removeAt(0);
117        }
118        mLock.unlock();
119
120        return event;
121    }
122
123    int mWorkWrite;
124
125    Mutex mLock;
126    Vector<KeyEvent*> mPendingKeys;
127};
128
129// ------------------------------------------------------------------------
130
131/*
132 * Native state for interacting with the NativeActivity class.
133 */
134struct NativeCode : public ANativeActivity {
135    NativeCode(void* _dlhandle, ANativeActivity_createFunc* _createFunc) {
136        memset((ANativeActivity*)this, sizeof(ANativeActivity), 0);
137        memset(&callbacks, sizeof(callbacks), 0);
138        dlhandle = _dlhandle;
139        createActivityFunc = _createFunc;
140        nativeWindow = NULL;
141        inputChannel = NULL;
142        nativeInputQueue = NULL;
143        mainWorkRead = mainWorkWrite = -1;
144    }
145
146    ~NativeCode() {
147        if (callbacks.onDestroy != NULL) {
148            callbacks.onDestroy(this);
149        }
150        if (env != NULL && clazz != NULL) {
151            env->DeleteGlobalRef(clazz);
152        }
153        if (pollLoop != NULL && mainWorkRead >= 0) {
154            pollLoop->removeCallback(mainWorkRead);
155        }
156        if (nativeInputQueue != NULL) {
157            nativeInputQueue->mWorkWrite = -1;
158        }
159        setSurface(NULL);
160        setInputChannel(NULL);
161        if (mainWorkRead >= 0) close(mainWorkRead);
162        if (mainWorkWrite >= 0) close(mainWorkWrite);
163        if (dlhandle != NULL) {
164            // for now don't unload...  we probably should clean this
165            // up and only keep one open dlhandle per proc, since there
166            // is really no benefit to unloading the code.
167            //dlclose(dlhandle);
168        }
169    }
170
171    void setSurface(jobject _surface) {
172        if (_surface != NULL) {
173            nativeWindow = android_Surface_getNativeWindow(env, _surface);
174        } else {
175            nativeWindow = NULL;
176        }
177    }
178
179    status_t setInputChannel(jobject _channel) {
180        if (inputChannel != NULL) {
181            delete nativeInputQueue;
182            env->DeleteGlobalRef(inputChannel);
183        }
184        inputChannel = NULL;
185        nativeInputQueue = NULL;
186        if (_channel != NULL) {
187            inputChannel = env->NewGlobalRef(_channel);
188            sp<InputChannel> ic =
189                    android_view_InputChannel_getInputChannel(env, _channel);
190            if (ic != NULL) {
191                nativeInputQueue = new MyInputQueue(ic, mainWorkWrite);
192                if (nativeInputQueue->getConsumer().initialize() != android::OK) {
193                    delete nativeInputQueue;
194                    nativeInputQueue = NULL;
195                    return UNKNOWN_ERROR;
196                }
197            } else {
198                return UNKNOWN_ERROR;
199            }
200        }
201        return OK;
202    }
203
204    ANativeActivityCallbacks callbacks;
205
206    void* dlhandle;
207    ANativeActivity_createFunc* createActivityFunc;
208
209    String8 internalDataPath;
210    String8 externalDataPath;
211
212    sp<ANativeWindow> nativeWindow;
213    jobject inputChannel;
214    struct MyInputQueue* nativeInputQueue;
215
216    // These are used to wake up the main thread to process work.
217    int mainWorkRead;
218    int mainWorkWrite;
219    sp<PollLoop> pollLoop;
220};
221
222void android_NativeActivity_setWindowFormat(
223        ANativeActivity* activity, int32_t format) {
224    NativeCode* code = static_cast<NativeCode*>(activity);
225    write_work(code->mainWorkWrite, CMD_SET_WINDOW_FORMAT, format);
226}
227
228void android_NativeActivity_setWindowFlags(
229        ANativeActivity* activity, int32_t values, int32_t mask) {
230    NativeCode* code = static_cast<NativeCode*>(activity);
231    write_work(code->mainWorkWrite, CMD_SET_WINDOW_FLAGS, values, mask);
232}
233
234// ------------------------------------------------------------------------
235
236/*
237 * Callback for handling native events on the application's main thread.
238 */
239static bool mainWorkCallback(int fd, int events, void* data) {
240    NativeCode* code = (NativeCode*)data;
241    if ((events & POLLIN) == 0) {
242        return true;
243    }
244
245    ActivityWork work;
246    if (!read_work(code->mainWorkRead, &work)) {
247        return true;
248    }
249    switch (work.cmd) {
250        case CMD_DEF_KEY: {
251            KeyEvent* keyEvent;
252            while ((keyEvent=code->nativeInputQueue->getNextEvent()) != NULL) {
253                jobject inputEventObj = android_view_KeyEvent_fromNative(
254                        code->env, keyEvent);
255                code->env->CallVoidMethod(code->clazz,
256                        gNativeActivityClassInfo.dispatchUnhandledKeyEvent, inputEventObj);
257                int32_t res = code->nativeInputQueue->getConsumer().sendFinishedSignal();
258                if (res != OK) {
259                    LOGW("Failed to send finished signal on channel '%s'.  status=%d",
260                            code->nativeInputQueue->getConsumer().getChannel()->getName().string(), res);
261                }
262            }
263        } break;
264        case CMD_SET_WINDOW_FORMAT: {
265            code->env->CallVoidMethod(code->clazz,
266                    gNativeActivityClassInfo.setWindowFormat, work.arg1);
267        } break;
268        case CMD_SET_WINDOW_FLAGS: {
269            code->env->CallVoidMethod(code->clazz,
270                    gNativeActivityClassInfo.setWindowFlags, work.arg1, work.arg2);
271        } break;
272        default:
273            LOGW("Unknown work command: %d", work.cmd);
274            break;
275    }
276
277    return true;
278}
279
280// ------------------------------------------------------------------------
281
282static jint
283loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jobject messageQueue,
284        jstring internalDataDir, jstring externalDataDir, int sdkVersion)
285{
286    const char* pathStr = env->GetStringUTFChars(path, NULL);
287    NativeCode* code = NULL;
288
289    void* handle = dlopen(pathStr, RTLD_LAZY);
290
291    env->ReleaseStringUTFChars(path, pathStr);
292
293    if (handle != NULL) {
294        code = new NativeCode(handle, (ANativeActivity_createFunc*)
295                dlsym(handle, "ANativeActivity_onCreate"));
296        if (code->createActivityFunc == NULL) {
297            LOGW("ANativeActivity_onCreate not found");
298            delete code;
299            return 0;
300        }
301
302        code->pollLoop = android_os_MessageQueue_getPollLoop(env, messageQueue);
303        if (code->pollLoop == NULL) {
304            LOGW("Unable to retrieve MessageQueue's PollLoop");
305            delete code;
306            return 0;
307        }
308
309        int msgpipe[2];
310        if (pipe(msgpipe)) {
311            LOGW("could not create pipe: %s", strerror(errno));
312            delete code;
313            return 0;
314        }
315        code->mainWorkRead = msgpipe[0];
316        code->mainWorkWrite = msgpipe[1];
317        code->pollLoop->setCallback(code->mainWorkRead, POLLIN, mainWorkCallback, code);
318
319        code->ANativeActivity::callbacks = &code->callbacks;
320        if (env->GetJavaVM(&code->vm) < 0) {
321            LOGW("NativeActivity GetJavaVM failed");
322            delete code;
323            return 0;
324        }
325        code->env = env;
326        code->clazz = env->NewGlobalRef(clazz);
327
328        const char* dirStr = env->GetStringUTFChars(internalDataDir, NULL);
329        code->internalDataPath = dirStr;
330        code->internalDataPath = code->internalDataPath.string();
331        env->ReleaseStringUTFChars(path, dirStr);
332
333        dirStr = env->GetStringUTFChars(externalDataDir, NULL);
334        code->externalDataPath = dirStr;
335        code->externalDataPath = code->externalDataPath.string();
336        env->ReleaseStringUTFChars(path, dirStr);
337
338        code->sdkVersion = sdkVersion;
339
340        code->createActivityFunc(code, NULL, 0);
341    }
342
343    return (jint)code;
344}
345
346static void
347unloadNativeCode_native(JNIEnv* env, jobject clazz, jint handle)
348{
349    if (handle != 0) {
350        NativeCode* code = (NativeCode*)handle;
351        delete code;
352    }
353}
354
355static void
356onStart_native(JNIEnv* env, jobject clazz, jint handle)
357{
358    if (handle != 0) {
359        NativeCode* code = (NativeCode*)handle;
360        if (code->callbacks.onStart != NULL) {
361            code->callbacks.onStart(code);
362        }
363    }
364}
365
366static void
367onResume_native(JNIEnv* env, jobject clazz, jint handle)
368{
369    if (handle != 0) {
370        NativeCode* code = (NativeCode*)handle;
371        if (code->callbacks.onResume != NULL) {
372            code->callbacks.onResume(code);
373        }
374    }
375}
376
377static void
378onSaveInstanceState_native(JNIEnv* env, jobject clazz, jint handle)
379{
380    if (handle != 0) {
381        NativeCode* code = (NativeCode*)handle;
382        if (code->callbacks.onSaveInstanceState != NULL) {
383            size_t len = 0;
384            code->callbacks.onSaveInstanceState(code, &len);
385        }
386    }
387}
388
389static void
390onPause_native(JNIEnv* env, jobject clazz, jint handle)
391{
392    if (handle != 0) {
393        NativeCode* code = (NativeCode*)handle;
394        if (code->callbacks.onPause != NULL) {
395            code->callbacks.onPause(code);
396        }
397    }
398}
399
400static void
401onStop_native(JNIEnv* env, jobject clazz, jint handle)
402{
403    if (handle != 0) {
404        NativeCode* code = (NativeCode*)handle;
405        if (code->callbacks.onStop != NULL) {
406            code->callbacks.onStop(code);
407        }
408    }
409}
410
411static void
412onLowMemory_native(JNIEnv* env, jobject clazz, jint handle)
413{
414    if (handle != 0) {
415        NativeCode* code = (NativeCode*)handle;
416        if (code->callbacks.onLowMemory != NULL) {
417            code->callbacks.onLowMemory(code);
418        }
419    }
420}
421
422static void
423onWindowFocusChanged_native(JNIEnv* env, jobject clazz, jint handle, jboolean focused)
424{
425    if (handle != 0) {
426        NativeCode* code = (NativeCode*)handle;
427        if (code->callbacks.onWindowFocusChanged != NULL) {
428            code->callbacks.onWindowFocusChanged(code, focused ? 1 : 0);
429        }
430    }
431}
432
433static void
434onSurfaceCreated_native(JNIEnv* env, jobject clazz, jint handle, jobject surface)
435{
436    if (handle != 0) {
437        NativeCode* code = (NativeCode*)handle;
438        code->setSurface(surface);
439        if (code->nativeWindow != NULL && code->callbacks.onNativeWindowCreated != NULL) {
440            code->callbacks.onNativeWindowCreated(code,
441                    code->nativeWindow.get());
442        }
443    }
444}
445
446static void
447onSurfaceChanged_native(JNIEnv* env, jobject clazz, jint handle, jobject surface,
448        jint format, jint width, jint height)
449{
450    if (handle != 0) {
451        NativeCode* code = (NativeCode*)handle;
452        sp<ANativeWindow> oldNativeWindow = code->nativeWindow;
453        code->setSurface(surface);
454        if (oldNativeWindow != code->nativeWindow) {
455            if (oldNativeWindow != NULL && code->callbacks.onNativeWindowDestroyed != NULL) {
456                code->callbacks.onNativeWindowDestroyed(code,
457                        oldNativeWindow.get());
458            }
459            if (code->nativeWindow != NULL && code->callbacks.onNativeWindowCreated != NULL) {
460                code->callbacks.onNativeWindowCreated(code,
461                        code->nativeWindow.get());
462            }
463        }
464    }
465}
466
467static void
468onSurfaceDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jobject surface)
469{
470    if (handle != 0) {
471        NativeCode* code = (NativeCode*)handle;
472        if (code->nativeWindow != NULL && code->callbacks.onNativeWindowDestroyed != NULL) {
473            code->callbacks.onNativeWindowDestroyed(code,
474                    code->nativeWindow.get());
475        }
476        code->setSurface(NULL);
477    }
478}
479
480static void
481onInputChannelCreated_native(JNIEnv* env, jobject clazz, jint handle, jobject channel)
482{
483    if (handle != 0) {
484        NativeCode* code = (NativeCode*)handle;
485        status_t err = code->setInputChannel(channel);
486        if (err != OK) {
487            jniThrowException(env, "java/lang/IllegalStateException",
488                    "Error setting input channel");
489            return;
490        }
491        if (code->callbacks.onInputQueueCreated != NULL) {
492            code->callbacks.onInputQueueCreated(code,
493                    code->nativeInputQueue);
494        }
495    }
496}
497
498static void
499onInputChannelDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jobject channel)
500{
501    if (handle != 0) {
502        NativeCode* code = (NativeCode*)handle;
503        if (code->nativeInputQueue != NULL
504                && code->callbacks.onInputQueueDestroyed != NULL) {
505            code->callbacks.onInputQueueDestroyed(code,
506                    code->nativeInputQueue);
507        }
508        code->setInputChannel(NULL);
509    }
510}
511
512static const JNINativeMethod g_methods[] = {
513    { "loadNativeCode", "(Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;I)I",
514            (void*)loadNativeCode_native },
515    { "unloadNativeCode", "(I)V", (void*)unloadNativeCode_native },
516    { "onStartNative", "(I)V", (void*)onStart_native },
517    { "onResumeNative", "(I)V", (void*)onResume_native },
518    { "onSaveInstanceStateNative", "(I)V", (void*)onSaveInstanceState_native },
519    { "onPauseNative", "(I)V", (void*)onPause_native },
520    { "onStopNative", "(I)V", (void*)onStop_native },
521    { "onLowMemoryNative", "(I)V", (void*)onLowMemory_native },
522    { "onWindowFocusChangedNative", "(IZ)V", (void*)onWindowFocusChanged_native },
523    { "onSurfaceCreatedNative", "(ILandroid/view/Surface;)V", (void*)onSurfaceCreated_native },
524    { "onSurfaceChangedNative", "(ILandroid/view/Surface;III)V", (void*)onSurfaceChanged_native },
525    { "onSurfaceDestroyedNative", "(I)V", (void*)onSurfaceDestroyed_native },
526    { "onInputChannelCreatedNative", "(ILandroid/view/InputChannel;)V", (void*)onInputChannelCreated_native },
527    { "onInputChannelDestroyedNative", "(ILandroid/view/InputChannel;)V", (void*)onInputChannelDestroyed_native },
528};
529
530static const char* const kNativeActivityPathName = "android/app/NativeActivity";
531
532#define FIND_CLASS(var, className) \
533        var = env->FindClass(className); \
534        LOG_FATAL_IF(! var, "Unable to find class " className); \
535        var = jclass(env->NewGlobalRef(var));
536
537#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
538        var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
539        LOG_FATAL_IF(! var, "Unable to find method" methodName);
540
541int register_android_app_NativeActivity(JNIEnv* env)
542{
543    //LOGD("register_android_app_NativeActivity");
544
545    FIND_CLASS(gNativeActivityClassInfo.clazz, kNativeActivityPathName);
546
547    GET_METHOD_ID(gNativeActivityClassInfo.dispatchUnhandledKeyEvent,
548            gNativeActivityClassInfo.clazz,
549            "dispatchUnhandledKeyEvent", "(Landroid/view/KeyEvent;)V");
550
551    GET_METHOD_ID(gNativeActivityClassInfo.setWindowFlags,
552            gNativeActivityClassInfo.clazz,
553            "setWindowFlags", "(II)V");
554    GET_METHOD_ID(gNativeActivityClassInfo.setWindowFormat,
555            gNativeActivityClassInfo.clazz,
556            "setWindowFormat", "(I)V");
557
558    return AndroidRuntime::registerNativeMethods(
559        env, kNativeActivityPathName,
560        g_methods, NELEM(g_methods));
561}
562
563} // namespace android
564