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