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