• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SkiaAndroidApp(JNIEnv * env,jobject androidApp)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 
~SkiaAndroidApp()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 
setTitle(const char * title) const68 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 
setUIState(const Json::Value & state) const74 void SkiaAndroidApp::setUIState(const Json::Value& state) const {
75     jstring jstr = fPThreadEnv->NewStringUTF(state.toStyledString().c_str());
76     fPThreadEnv->CallVoidMethod(fAndroidApp, fSetStateMethodID, jstr);
77     fPThreadEnv->DeleteLocalRef(jstr);
78 }
79 
postMessage(const Message & message) const80 void SkiaAndroidApp::postMessage(const Message& message) const {
81     SkDEBUGCODE(auto writeSize =) write(fPipes[1], &message, sizeof(message));
82     SkASSERT(writeSize == sizeof(message));
83 }
84 
readMessage(Message * message) const85 void SkiaAndroidApp::readMessage(Message* message) const {
86     SkDEBUGCODE(auto readSize =) read(fPipes[0], message, sizeof(Message));
87     SkASSERT(readSize == sizeof(Message));
88 }
89 
message_callback(int fd,int events,void * data)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->setContentRect(0, 0, width, height);
127             window_android->paintIfNeeded();
128             break;
129         }
130         case kSurfaceDestroyed: {
131             if (skiaAndroidApp->fNativeWindow) {
132                 auto window_android = (Window_android*)skiaAndroidApp->fWindow;
133                 window_android->onDisplayDestroyed();
134                 ANativeWindow_release(skiaAndroidApp->fNativeWindow);
135                 skiaAndroidApp->fNativeWindow = nullptr;
136             }
137             break;
138         }
139         case kKeyPressed: {
140             auto it = ANDROID_TO_WINDOW_KEYMAP.find(message.fKeycode);
141             SkASSERT(it != ANDROID_TO_WINDOW_KEYMAP.end());
142             // No modifier is supported so far
143             skiaAndroidApp->fWindow->onKey(it->second, Window::kDown_InputState, 0);
144             skiaAndroidApp->fWindow->onKey(it->second, Window::kUp_InputState, 0);
145             break;
146         }
147         case kTouched: {
148             auto it = ANDROID_TO_WINDOW_STATEMAP.find(message.fTouchState);
149             if (it != ANDROID_TO_WINDOW_STATEMAP.end()) {
150                 skiaAndroidApp->fWindow->onTouch(message.fTouchOwner, it->second, message.fTouchX,
151                                                  message.fTouchY);
152             } else {
153                 SkDebugf("Unknown Touch State: %d\n", message.fTouchState);
154             }
155             break;
156         }
157         case kUIStateChanged: {
158             skiaAndroidApp->fWindow->onUIStateChanged(*message.stateName, *message.stateValue);
159             delete message.stateName;
160             delete message.stateValue;
161             break;
162         }
163         default: {
164             // do nothing
165         }
166     }
167 
168     return 1;  // continue receiving callbacks
169 }
170 
pthread_main(void * arg)171 void* SkiaAndroidApp::pthread_main(void* arg) {
172     SkDebugf("pthread_main begins");
173 
174     auto skiaAndroidApp = (SkiaAndroidApp*)arg;
175 
176     // Because JNIEnv is thread sensitive, we need AttachCurrentThread to set our fPThreadEnv
177     skiaAndroidApp->fJavaVM->AttachCurrentThread(&(skiaAndroidApp->fPThreadEnv), nullptr);
178 
179     ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
180     pipe(skiaAndroidApp->fPipes);
181     ALooper_addFd(looper, skiaAndroidApp->fPipes[0], LOOPER_ID_MESSAGEPIPE, ALOOPER_EVENT_INPUT,
182                   message_callback, skiaAndroidApp);
183 
184     static const char* gCmdLine[] = {
185         "viewer",
186         // TODO: figure out how to use am start with extra params to pass in additional arguments at
187         // runtime. Or better yet make an in app switch to enable
188         // "--atrace",
189     };
190 
191     skiaAndroidApp->fApp = Application::Create(SK_ARRAY_COUNT(gCmdLine),
192                                                const_cast<char**>(gCmdLine),
193                                                skiaAndroidApp);
194 
195     while (true) {
196         const int ident = ALooper_pollAll(0, nullptr, nullptr, nullptr);
197 
198         if (ident >= 0) {
199             SkDebugf("Unhandled ALooper_pollAll ident=%d !", ident);
200         } else {
201             skiaAndroidApp->fApp->onIdle();
202         }
203     }
204 
205     SkDebugf("pthread_main ends");
206 
207     return nullptr;
208 }
209 
210 extern "C"  // extern "C" is needed for JNI (although the method itself is in C++)
211     JNIEXPORT jlong JNICALL
Java_org_skia_viewer_ViewerApplication_createNativeApp(JNIEnv * env,jobject application)212     Java_org_skia_viewer_ViewerApplication_createNativeApp(JNIEnv* env, jobject application) {
213     SkiaAndroidApp* skiaAndroidApp = new SkiaAndroidApp(env, application);
214     return (jlong)((size_t)skiaAndroidApp);
215 }
216 
Java_org_skia_viewer_ViewerApplication_destroyNativeApp(JNIEnv * env,jobject application,jlong handle)217 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerApplication_destroyNativeApp(
218     JNIEnv* env, jobject application, jlong handle) {
219     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
220     skiaAndroidApp->postMessage(Message(kDestroyApp));
221 }
222 
Java_org_skia_viewer_ViewerActivity_onSurfaceCreated(JNIEnv * env,jobject activity,jlong handle,jobject surface)223 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceCreated(
224     JNIEnv* env, jobject activity, jlong handle, jobject surface) {
225     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
226     Message message(kSurfaceCreated);
227     message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
228     skiaAndroidApp->postMessage(message);
229 }
230 
Java_org_skia_viewer_ViewerActivity_onSurfaceChanged(JNIEnv * env,jobject activity,jlong handle,jobject surface)231 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceChanged(
232     JNIEnv* env, jobject activity, jlong handle, jobject surface) {
233     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
234     Message message(kSurfaceChanged);
235     message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
236     skiaAndroidApp->postMessage(message);
237 }
238 
Java_org_skia_viewer_ViewerActivity_onSurfaceDestroyed(JNIEnv * env,jobject activity,jlong handle)239 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceDestroyed(
240     JNIEnv* env, jobject activity, jlong handle) {
241     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
242     skiaAndroidApp->postMessage(Message(kSurfaceDestroyed));
243 }
244 
Java_org_skia_viewer_ViewerActivity_onKeyPressed(JNIEnv * env,jobject activity,jlong handle,jint keycode)245 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onKeyPressed(JNIEnv* env,
246                                                                                    jobject activity,
247                                                                                    jlong handle,
248                                                                                    jint keycode) {
249     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
250     Message message(kKeyPressed);
251     message.fKeycode = keycode;
252     skiaAndroidApp->postMessage(message);
253 }
254 
Java_org_skia_viewer_ViewerActivity_onTouched(JNIEnv * env,jobject activity,jlong handle,jint owner,jint state,jfloat x,jfloat y)255 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onTouched(
256     JNIEnv* env, jobject activity, jlong handle, jint owner, jint state, jfloat x, jfloat y) {
257     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
258     Message message(kTouched);
259     message.fTouchOwner = owner;
260     message.fTouchState = state;
261     message.fTouchX = x;
262     message.fTouchY = y;
263     skiaAndroidApp->postMessage(message);
264 }
265 
Java_org_skia_viewer_ViewerActivity_onUIStateChanged(JNIEnv * env,jobject activity,jlong handle,jstring stateName,jstring stateValue)266 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onUIStateChanged(
267     JNIEnv* env, jobject activity, jlong handle, jstring stateName, jstring stateValue) {
268     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
269     Message message(kUIStateChanged);
270     const char* nameChars = env->GetStringUTFChars(stateName, nullptr);
271     const char* valueChars = env->GetStringUTFChars(stateValue, nullptr);
272     message.stateName = new SkString(nameChars);
273     message.stateValue = new SkString(valueChars);
274     skiaAndroidApp->postMessage(message);
275     env->ReleaseStringUTFChars(stateName, nameChars);
276     env->ReleaseStringUTFChars(stateValue, valueChars);
277 }
278 
279 }  // namespace sk_app
280