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 char * state) const74 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
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