aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/sk_app/android/surface_glue_android.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/sk_app/android/surface_glue_android.cpp')
-rw-r--r--tools/sk_app/android/surface_glue_android.cpp278
1 files changed, 278 insertions, 0 deletions
diff --git a/tools/sk_app/android/surface_glue_android.cpp b/tools/sk_app/android/surface_glue_android.cpp
new file mode 100644
index 0000000000..9c734247db
--- /dev/null
+++ b/tools/sk_app/android/surface_glue_android.cpp
@@ -0,0 +1,278 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "surface_glue_android.h"
+
+#include <jni.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <unordered_map>
+
+#include <android/input.h>
+#include <android/keycodes.h>
+#include <android/looper.h>
+#include <android/native_window_jni.h>
+
+#include "../Application.h"
+#include "SkTypes.h"
+#include "SkUtils.h"
+#include "Window_android.h"
+
+namespace sk_app {
+
+static const int LOOPER_ID_MESSAGEPIPE = 1;
+
+static const std::unordered_map<int, Window::Key> ANDROID_TO_WINDOW_KEYMAP({
+ {AKEYCODE_SOFT_LEFT, Window::Key::kLeft},
+ {AKEYCODE_SOFT_RIGHT, Window::Key::kRight}
+});
+
+static const std::unordered_map<int, Window::InputState> ANDROID_TO_WINDOW_STATEMAP({
+ {AMOTION_EVENT_ACTION_DOWN, Window::kDown_InputState},
+ {AMOTION_EVENT_ACTION_POINTER_DOWN, Window::kDown_InputState},
+ {AMOTION_EVENT_ACTION_UP, Window::kUp_InputState},
+ {AMOTION_EVENT_ACTION_POINTER_UP, Window::kUp_InputState},
+ {AMOTION_EVENT_ACTION_MOVE, Window::kMove_InputState},
+ {AMOTION_EVENT_ACTION_CANCEL, Window::kUp_InputState},
+});
+
+SkiaAndroidApp::SkiaAndroidApp(JNIEnv* env, jobject androidApp) {
+ env->GetJavaVM(&fJavaVM);
+ fAndroidApp = env->NewGlobalRef(androidApp);
+ jclass cls = env->GetObjectClass(fAndroidApp);
+ fSetTitleMethodID = env->GetMethodID(cls, "setTitle", "(Ljava/lang/String;)V");
+ fSetStateMethodID = env->GetMethodID(cls, "setState", "(Ljava/lang/String;)V");
+ fNativeWindow = nullptr;
+ pthread_create(&fThread, nullptr, pthread_main, this);
+}
+
+SkiaAndroidApp::~SkiaAndroidApp() {
+ fPThreadEnv->DeleteGlobalRef(fAndroidApp);
+ if (fWindow) {
+ fWindow->detach();
+ }
+ if (fNativeWindow) {
+ ANativeWindow_release(fNativeWindow);
+ fNativeWindow = nullptr;
+ }
+ if (fApp) {
+ delete fApp;
+ }
+}
+
+void SkiaAndroidApp::setTitle(const char* title) const {
+ jstring titleString = fPThreadEnv->NewStringUTF(title);
+ fPThreadEnv->CallVoidMethod(fAndroidApp, fSetTitleMethodID, titleString);
+ fPThreadEnv->DeleteLocalRef(titleString);
+}
+
+void SkiaAndroidApp::setUIState(const char* state) const {
+ jstring jstr = fPThreadEnv->NewStringUTF(state);
+ fPThreadEnv->CallVoidMethod(fAndroidApp, fSetStateMethodID, jstr);
+ fPThreadEnv->DeleteLocalRef(jstr);
+}
+
+void SkiaAndroidApp::postMessage(const Message& message) const {
+ SkDEBUGCODE(auto writeSize =) write(fPipes[1], &message, sizeof(message));
+ SkASSERT(writeSize == sizeof(message));
+}
+
+void SkiaAndroidApp::readMessage(Message* message) const {
+ SkDEBUGCODE(auto readSize =) read(fPipes[0], message, sizeof(Message));
+ SkASSERT(readSize == sizeof(Message));
+}
+
+int SkiaAndroidApp::message_callback(int fd, int events, void* data) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)data;
+ Message message;
+ skiaAndroidApp->readMessage(&message);
+ SkASSERT(message.fType != kUndefined);
+
+ switch (message.fType) {
+ case kDestroyApp: {
+ delete skiaAndroidApp;
+ pthread_exit(nullptr);
+ return 0;
+ }
+ case kContentInvalidated: {
+ ((Window_android*)skiaAndroidApp->fWindow)->paintIfNeeded();
+ break;
+ }
+ case kSurfaceCreated: {
+ SkASSERT(!skiaAndroidApp->fNativeWindow && message.fNativeWindow);
+ skiaAndroidApp->fNativeWindow = message.fNativeWindow;
+ auto window_android = (Window_android*)skiaAndroidApp->fWindow;
+ window_android->initDisplay(skiaAndroidApp->fNativeWindow);
+ ((Window_android*)skiaAndroidApp->fWindow)->paintIfNeeded();
+ break;
+ }
+ case kSurfaceChanged: {
+ SkASSERT(message.fNativeWindow);
+ int width = ANativeWindow_getWidth(skiaAndroidApp->fNativeWindow);
+ int height = ANativeWindow_getHeight(skiaAndroidApp->fNativeWindow);
+ auto window_android = (Window_android*)skiaAndroidApp->fWindow;
+ if (message.fNativeWindow != skiaAndroidApp->fNativeWindow) {
+ window_android->onDisplayDestroyed();
+ ANativeWindow_release(skiaAndroidApp->fNativeWindow);
+ skiaAndroidApp->fNativeWindow = message.fNativeWindow;
+ window_android->initDisplay(skiaAndroidApp->fNativeWindow);
+ }
+ window_android->onResize(width, height);
+ window_android->paintIfNeeded();
+ break;
+ }
+ case kSurfaceDestroyed: {
+ if (skiaAndroidApp->fNativeWindow) {
+ auto window_android = (Window_android*)skiaAndroidApp->fWindow;
+ window_android->onDisplayDestroyed();
+ ANativeWindow_release(skiaAndroidApp->fNativeWindow);
+ skiaAndroidApp->fNativeWindow = nullptr;
+ }
+ break;
+ }
+ case kKeyPressed: {
+ auto it = ANDROID_TO_WINDOW_KEYMAP.find(message.fKeycode);
+ SkASSERT(it != ANDROID_TO_WINDOW_KEYMAP.end());
+ // No modifier is supported so far
+ skiaAndroidApp->fWindow->onKey(it->second, Window::kDown_InputState, 0);
+ skiaAndroidApp->fWindow->onKey(it->second, Window::kUp_InputState, 0);
+ break;
+ }
+ case kTouched: {
+ auto it = ANDROID_TO_WINDOW_STATEMAP.find(message.fTouchState);
+ if (it != ANDROID_TO_WINDOW_STATEMAP.end()) {
+ skiaAndroidApp->fWindow->onTouch(message.fTouchOwner, it->second, message.fTouchX,
+ message.fTouchY);
+ } else {
+ SkDebugf("Unknown Touch State: %d\n", message.fTouchState);
+ }
+ break;
+ }
+ case kUIStateChanged: {
+ skiaAndroidApp->fWindow->onUIStateChanged(*message.stateName, *message.stateValue);
+ delete message.stateName;
+ delete message.stateValue;
+ break;
+ }
+ default: {
+ // do nothing
+ }
+ }
+
+ return 1; // continue receiving callbacks
+}
+
+void* SkiaAndroidApp::pthread_main(void* arg) {
+ SkDebugf("pthread_main begins");
+
+ auto skiaAndroidApp = (SkiaAndroidApp*)arg;
+
+ // Because JNIEnv is thread sensitive, we need AttachCurrentThread to set our fPThreadEnv
+ skiaAndroidApp->fJavaVM->AttachCurrentThread(&(skiaAndroidApp->fPThreadEnv), nullptr);
+
+ ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
+ pipe(skiaAndroidApp->fPipes);
+ ALooper_addFd(looper, skiaAndroidApp->fPipes[0], LOOPER_ID_MESSAGEPIPE, ALOOPER_EVENT_INPUT,
+ message_callback, skiaAndroidApp);
+
+ static const char* gCmdLine[] = {
+ "viewer",
+ // TODO: figure out how to use am start with extra params to pass in additional arguments at
+ // runtime. Or better yet make an in app switch to enable
+ // "--atrace",
+ };
+
+ skiaAndroidApp->fApp = Application::Create(SK_ARRAY_COUNT(gCmdLine),
+ const_cast<char**>(gCmdLine),
+ skiaAndroidApp);
+
+ while (true) {
+ const int ident = ALooper_pollAll(0, nullptr, nullptr, nullptr);
+
+ if (ident >= 0) {
+ SkDebugf("Unhandled ALooper_pollAll ident=%d !", ident);
+ } else {
+ skiaAndroidApp->fApp->onIdle();
+ }
+ }
+
+ SkDebugf("pthread_main ends");
+
+ return nullptr;
+}
+
+extern "C" // extern "C" is needed for JNI (although the method itself is in C++)
+ JNIEXPORT jlong JNICALL
+ Java_org_skia_viewer_ViewerApplication_createNativeApp(JNIEnv* env, jobject application) {
+ SkiaAndroidApp* skiaAndroidApp = new SkiaAndroidApp(env, application);
+ return (jlong)((size_t)skiaAndroidApp);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerApplication_destroyNativeApp(
+ JNIEnv* env, jobject application, jlong handle) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ skiaAndroidApp->postMessage(Message(kDestroyApp));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceCreated(
+ JNIEnv* env, jobject activity, jlong handle, jobject surface) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ Message message(kSurfaceCreated);
+ message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
+ skiaAndroidApp->postMessage(message);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceChanged(
+ JNIEnv* env, jobject activity, jlong handle, jobject surface) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ Message message(kSurfaceChanged);
+ message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
+ skiaAndroidApp->postMessage(message);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceDestroyed(
+ JNIEnv* env, jobject activity, jlong handle) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ skiaAndroidApp->postMessage(Message(kSurfaceDestroyed));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onKeyPressed(JNIEnv* env,
+ jobject activity,
+ jlong handle,
+ jint keycode) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ Message message(kKeyPressed);
+ message.fKeycode = keycode;
+ skiaAndroidApp->postMessage(message);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onTouched(
+ JNIEnv* env, jobject activity, jlong handle, jint owner, jint state, jfloat x, jfloat y) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ Message message(kTouched);
+ message.fTouchOwner = owner;
+ message.fTouchState = state;
+ message.fTouchX = x;
+ message.fTouchY = y;
+ skiaAndroidApp->postMessage(message);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onUIStateChanged(
+ JNIEnv* env, jobject activity, jlong handle, jstring stateName, jstring stateValue) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ Message message(kUIStateChanged);
+ const char* nameChars = env->GetStringUTFChars(stateName, nullptr);
+ const char* valueChars = env->GetStringUTFChars(stateValue, nullptr);
+ message.stateName = new SkString(nameChars);
+ message.stateValue = new SkString(valueChars);
+ skiaAndroidApp->postMessage(message);
+ env->ReleaseStringUTFChars(stateName, nameChars);
+ env->ReleaseStringUTFChars(stateValue, valueChars);
+}
+
+} // namespace sk_app