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