Home | History | Annotate | Download | only in android
      1 /*
      2 * Copyright 2016 Google Inc.
      3 *
      4 * Use of this source code is governed by a BSD-style license that can be
      5 * found in the LICENSE file.
      6 */
      7 
      8 #include "surface_glue_android.h"
      9 
     10 #include <jni.h>
     11 #include <pthread.h>
     12 #include <stdio.h>
     13 #include <unistd.h>
     14 #include <unordered_map>
     15 
     16 #include <android/input.h>
     17 #include <android/keycodes.h>
     18 #include <android/looper.h>
     19 #include <android/native_window_jni.h>
     20 
     21 #include "../Application.h"
     22 #include "SkTypes.h"
     23 #include "SkUtils.h"
     24 #include "Window_android.h"
     25 
     26 namespace sk_app {
     27 
     28 static const int LOOPER_ID_MESSAGEPIPE = 1;
     29 
     30 static const std::unordered_map<int, Window::Key> ANDROID_TO_WINDOW_KEYMAP({
     31     {AKEYCODE_SOFT_LEFT, Window::Key::kLeft},
     32     {AKEYCODE_SOFT_RIGHT, Window::Key::kRight}
     33 });
     34 
     35 static const std::unordered_map<int, Window::InputState> ANDROID_TO_WINDOW_STATEMAP({
     36     {AMOTION_EVENT_ACTION_DOWN, Window::kDown_InputState},
     37     {AMOTION_EVENT_ACTION_POINTER_DOWN, Window::kDown_InputState},
     38     {AMOTION_EVENT_ACTION_UP, Window::kUp_InputState},
     39     {AMOTION_EVENT_ACTION_POINTER_UP, Window::kUp_InputState},
     40     {AMOTION_EVENT_ACTION_MOVE, Window::kMove_InputState},
     41     {AMOTION_EVENT_ACTION_CANCEL, Window::kUp_InputState},
     42 });
     43 
     44 SkiaAndroidApp::SkiaAndroidApp(JNIEnv* env, jobject androidApp) {
     45     env->GetJavaVM(&fJavaVM);
     46     fAndroidApp = env->NewGlobalRef(androidApp);
     47     jclass cls = env->GetObjectClass(fAndroidApp);
     48     fSetTitleMethodID = env->GetMethodID(cls, "setTitle", "(Ljava/lang/String;)V");
     49     fSetStateMethodID = env->GetMethodID(cls, "setState", "(Ljava/lang/String;)V");
     50     fNativeWindow = nullptr;
     51     pthread_create(&fThread, nullptr, pthread_main, this);
     52 }
     53 
     54 SkiaAndroidApp::~SkiaAndroidApp() {
     55     fPThreadEnv->DeleteGlobalRef(fAndroidApp);
     56     if (fWindow) {
     57         fWindow->detach();
     58     }
     59     if (fNativeWindow) {
     60         ANativeWindow_release(fNativeWindow);
     61         fNativeWindow = nullptr;
     62     }
     63     if (fApp) {
     64         delete fApp;
     65     }
     66 }
     67 
     68 void SkiaAndroidApp::setTitle(const char* title) const {
     69     jstring titleString = fPThreadEnv->NewStringUTF(title);
     70     fPThreadEnv->CallVoidMethod(fAndroidApp, fSetTitleMethodID, titleString);
     71     fPThreadEnv->DeleteLocalRef(titleString);
     72 }
     73 
     74 void SkiaAndroidApp::setUIState(const char* state) const {
     75     jstring jstr = fPThreadEnv->NewStringUTF(state);
     76     fPThreadEnv->CallVoidMethod(fAndroidApp, fSetStateMethodID, jstr);
     77     fPThreadEnv->DeleteLocalRef(jstr);
     78 }
     79 
     80 void SkiaAndroidApp::postMessage(const Message& message) const {
     81     SkDEBUGCODE(auto writeSize =) write(fPipes[1], &message, sizeof(message));
     82     SkASSERT(writeSize == sizeof(message));
     83 }
     84 
     85 void SkiaAndroidApp::readMessage(Message* message) const {
     86     SkDEBUGCODE(auto readSize =) read(fPipes[0], message, sizeof(Message));
     87     SkASSERT(readSize == sizeof(Message));
     88 }
     89 
     90 int SkiaAndroidApp::message_callback(int fd, int events, void* data) {
     91     auto skiaAndroidApp = (SkiaAndroidApp*)data;
     92     Message message;
     93     skiaAndroidApp->readMessage(&message);
     94     SkASSERT(message.fType != kUndefined);
     95 
     96     switch (message.fType) {
     97         case kDestroyApp: {
     98             delete skiaAndroidApp;
     99             pthread_exit(nullptr);
    100             return 0;
    101         }
    102         case kContentInvalidated: {
    103             ((Window_android*)skiaAndroidApp->fWindow)->paintIfNeeded();
    104             break;
    105         }
    106         case kSurfaceCreated: {
    107             SkASSERT(!skiaAndroidApp->fNativeWindow && message.fNativeWindow);
    108             skiaAndroidApp->fNativeWindow = message.fNativeWindow;
    109             auto window_android = (Window_android*)skiaAndroidApp->fWindow;
    110             window_android->initDisplay(skiaAndroidApp->fNativeWindow);
    111             ((Window_android*)skiaAndroidApp->fWindow)->paintIfNeeded();
    112             break;
    113         }
    114         case kSurfaceChanged: {
    115             SkASSERT(message.fNativeWindow);
    116             int width = ANativeWindow_getWidth(skiaAndroidApp->fNativeWindow);
    117             int height = ANativeWindow_getHeight(skiaAndroidApp->fNativeWindow);
    118             auto window_android = (Window_android*)skiaAndroidApp->fWindow;
    119             if (message.fNativeWindow != skiaAndroidApp->fNativeWindow) {
    120                 window_android->onDisplayDestroyed();
    121                 ANativeWindow_release(skiaAndroidApp->fNativeWindow);
    122                 skiaAndroidApp->fNativeWindow = message.fNativeWindow;
    123                 window_android->initDisplay(skiaAndroidApp->fNativeWindow);
    124             }
    125             window_android->onResize(width, height);
    126             window_android->paintIfNeeded();
    127             break;
    128         }
    129         case kSurfaceDestroyed: {
    130             if (skiaAndroidApp->fNativeWindow) {
    131                 auto window_android = (Window_android*)skiaAndroidApp->fWindow;
    132                 window_android->onDisplayDestroyed();
    133                 ANativeWindow_release(skiaAndroidApp->fNativeWindow);
    134                 skiaAndroidApp->fNativeWindow = nullptr;
    135             }
    136             break;
    137         }
    138         case kKeyPressed: {
    139             auto it = ANDROID_TO_WINDOW_KEYMAP.find(message.fKeycode);
    140             SkASSERT(it != ANDROID_TO_WINDOW_KEYMAP.end());
    141             // No modifier is supported so far
    142             skiaAndroidApp->fWindow->onKey(it->second, Window::kDown_InputState, 0);
    143             skiaAndroidApp->fWindow->onKey(it->second, Window::kUp_InputState, 0);
    144             break;
    145         }
    146         case kTouched: {
    147             auto it = ANDROID_TO_WINDOW_STATEMAP.find(message.fTouchState);
    148             if (it != ANDROID_TO_WINDOW_STATEMAP.end()) {
    149                 skiaAndroidApp->fWindow->onTouch(message.fTouchOwner, it->second, message.fTouchX,
    150                                                  message.fTouchY);
    151             } else {
    152                 SkDebugf("Unknown Touch State: %d\n", message.fTouchState);
    153             }
    154             break;
    155         }
    156         case kUIStateChanged: {
    157             skiaAndroidApp->fWindow->onUIStateChanged(*message.stateName, *message.stateValue);
    158             delete message.stateName;
    159             delete message.stateValue;
    160             break;
    161         }
    162         default: {
    163             // do nothing
    164         }
    165     }
    166 
    167     return 1;  // continue receiving callbacks
    168 }
    169 
    170 void* SkiaAndroidApp::pthread_main(void* arg) {
    171     SkDebugf("pthread_main begins");
    172 
    173     auto skiaAndroidApp = (SkiaAndroidApp*)arg;
    174 
    175     // Because JNIEnv is thread sensitive, we need AttachCurrentThread to set our fPThreadEnv
    176     skiaAndroidApp->fJavaVM->AttachCurrentThread(&(skiaAndroidApp->fPThreadEnv), nullptr);
    177 
    178     ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
    179     pipe(skiaAndroidApp->fPipes);
    180     ALooper_addFd(looper, skiaAndroidApp->fPipes[0], LOOPER_ID_MESSAGEPIPE, ALOOPER_EVENT_INPUT,
    181                   message_callback, skiaAndroidApp);
    182 
    183     static const char* gCmdLine[] = {
    184         "viewer",
    185         // TODO: figure out how to use am start with extra params to pass in additional arguments at
    186         // runtime. Or better yet make an in app switch to enable
    187         // "--atrace",
    188     };
    189 
    190     skiaAndroidApp->fApp = Application::Create(SK_ARRAY_COUNT(gCmdLine),
    191                                                const_cast<char**>(gCmdLine),
    192                                                skiaAndroidApp);
    193 
    194     while (true) {
    195         const int ident = ALooper_pollAll(0, nullptr, nullptr, nullptr);
    196 
    197         if (ident >= 0) {
    198             SkDebugf("Unhandled ALooper_pollAll ident=%d !", ident);
    199         } else {
    200             skiaAndroidApp->fApp->onIdle();
    201         }
    202     }
    203 
    204     SkDebugf("pthread_main ends");
    205 
    206     return nullptr;
    207 }
    208 
    209 extern "C"  // extern "C" is needed for JNI (although the method itself is in C++)
    210     JNIEXPORT jlong JNICALL
    211     Java_org_skia_viewer_ViewerApplication_createNativeApp(JNIEnv* env, jobject application) {
    212     SkiaAndroidApp* skiaAndroidApp = new SkiaAndroidApp(env, application);
    213     return (jlong)((size_t)skiaAndroidApp);
    214 }
    215 
    216 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerApplication_destroyNativeApp(
    217     JNIEnv* env, jobject application, jlong handle) {
    218     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
    219     skiaAndroidApp->postMessage(Message(kDestroyApp));
    220 }
    221 
    222 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceCreated(
    223     JNIEnv* env, jobject activity, jlong handle, jobject surface) {
    224     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
    225     Message message(kSurfaceCreated);
    226     message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
    227     skiaAndroidApp->postMessage(message);
    228 }
    229 
    230 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceChanged(
    231     JNIEnv* env, jobject activity, jlong handle, jobject surface) {
    232     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
    233     Message message(kSurfaceChanged);
    234     message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
    235     skiaAndroidApp->postMessage(message);
    236 }
    237 
    238 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceDestroyed(
    239     JNIEnv* env, jobject activity, jlong handle) {
    240     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
    241     skiaAndroidApp->postMessage(Message(kSurfaceDestroyed));
    242 }
    243 
    244 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onKeyPressed(JNIEnv* env,
    245                                                                                    jobject activity,
    246                                                                                    jlong handle,
    247                                                                                    jint keycode) {
    248     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
    249     Message message(kKeyPressed);
    250     message.fKeycode = keycode;
    251     skiaAndroidApp->postMessage(message);
    252 }
    253 
    254 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onTouched(
    255     JNIEnv* env, jobject activity, jlong handle, jint owner, jint state, jfloat x, jfloat y) {
    256     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
    257     Message message(kTouched);
    258     message.fTouchOwner = owner;
    259     message.fTouchState = state;
    260     message.fTouchX = x;
    261     message.fTouchY = y;
    262     skiaAndroidApp->postMessage(message);
    263 }
    264 
    265 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onUIStateChanged(
    266     JNIEnv* env, jobject activity, jlong handle, jstring stateName, jstring stateValue) {
    267     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
    268     Message message(kUIStateChanged);
    269     const char* nameChars = env->GetStringUTFChars(stateName, nullptr);
    270     const char* valueChars = env->GetStringUTFChars(stateValue, nullptr);
    271     message.stateName = new SkString(nameChars);
    272     message.stateValue = new SkString(valueChars);
    273     skiaAndroidApp->postMessage(message);
    274     env->ReleaseStringUTFChars(stateName, nameChars);
    275     env->ReleaseStringUTFChars(stateValue, valueChars);
    276 }
    277 
    278 }  // namespace sk_app
    279