• 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->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 
pthread_main(void * arg)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
Java_org_skia_viewer_ViewerApplication_createNativeApp(JNIEnv * env,jobject application)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 
Java_org_skia_viewer_ViewerApplication_destroyNativeApp(JNIEnv * env,jobject application,jlong handle)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 
Java_org_skia_viewer_ViewerActivity_onSurfaceCreated(JNIEnv * env,jobject activity,jlong handle,jobject surface)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 
Java_org_skia_viewer_ViewerActivity_onSurfaceChanged(JNIEnv * env,jobject activity,jlong handle,jobject surface)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 
Java_org_skia_viewer_ViewerActivity_onSurfaceDestroyed(JNIEnv * env,jobject activity,jlong handle)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 
Java_org_skia_viewer_ViewerActivity_onKeyPressed(JNIEnv * env,jobject activity,jlong handle,jint keycode)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 
Java_org_skia_viewer_ViewerActivity_onTouched(JNIEnv * env,jobject activity,jlong handle,jint owner,jint state,jfloat x,jfloat y)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 
Java_org_skia_viewer_ViewerActivity_onUIStateChanged(JNIEnv * env,jobject activity,jlong handle,jstring stateName,jstring stateValue)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