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