xref: /aosp_15_r20/external/skia/tools/sk_app/android/surface_glue_android.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 "tools/sk_app/android/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 "include/core/SkTypes.h"
24 #include "include/private/base/SkTo.h"
25 #include "src/base/SkUTF.h"
26 #include "tools/ResourceFactory.h"
27 #include "tools/sk_app/Application.h"
28 #include "tools/sk_app/android/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, skui::Key> ANDROID_TO_WINDOW_KEYMAP({
58     {AKEYCODE_SOFT_LEFT,  skui::Key::kLeft },
59     {AKEYCODE_SOFT_RIGHT, skui::Key::kRight}
60 });
61 
62 static const std::unordered_map<int, skui::InputState> ANDROID_TO_WINDOW_STATEMAP({
63     {AMOTION_EVENT_ACTION_DOWN,         skui::InputState::kDown },
64     {AMOTION_EVENT_ACTION_POINTER_DOWN, skui::InputState::kDown },
65     {AMOTION_EVENT_ACTION_UP,           skui::InputState::kUp   },
66     {AMOTION_EVENT_ACTION_POINTER_UP,   skui::InputState::kUp   },
67     {AMOTION_EVENT_ACTION_MOVE,         skui::InputState::kMove },
68     {AMOTION_EVENT_ACTION_CANCEL,       skui::InputState::kUp   },
69 });
70 
SkiaAndroidApp(JNIEnv * env,jobject androidApp,jobjectArray args)71 SkiaAndroidApp::SkiaAndroidApp(JNIEnv* env, jobject androidApp, jobjectArray args) {
72     env->GetJavaVM(&fJavaVM);
73     fAndroidApp = env->NewGlobalRef(androidApp);
74     fArgs = static_cast<jobjectArray>(env->NewGlobalRef(args));
75     jclass cls = env->GetObjectClass(fAndroidApp);
76     fSetTitleMethodID = env->GetMethodID(cls, "setTitle", "(Ljava/lang/String;)V");
77     fSetStateMethodID = env->GetMethodID(cls, "setState", "(Ljava/lang/String;)V");
78     fNativeWindow = nullptr;
79     pthread_create(&fThread, nullptr, pthread_main, this);
80 }
81 
~SkiaAndroidApp()82 SkiaAndroidApp::~SkiaAndroidApp() {
83     fPThreadEnv->DeleteGlobalRef(fAndroidApp);
84     fPThreadEnv->DeleteGlobalRef(fArgs);
85     if (fWindow) {
86         fWindow->detach();
87     }
88     if (fNativeWindow) {
89         ANativeWindow_release(fNativeWindow);
90         fNativeWindow = nullptr;
91     }
92     if (fApp) {
93         delete fApp;
94     }
95 }
96 
setTitle(const char * title) const97 void SkiaAndroidApp::setTitle(const char* title) const {
98     jstring titleString = fPThreadEnv->NewStringUTF(title);
99     fPThreadEnv->CallVoidMethod(fAndroidApp, fSetTitleMethodID, titleString);
100     fPThreadEnv->DeleteLocalRef(titleString);
101 }
102 
setUIState(const char * state) const103 void SkiaAndroidApp::setUIState(const char* state) const {
104     jstring jstr = fPThreadEnv->NewStringUTF(state);
105     fPThreadEnv->CallVoidMethod(fAndroidApp, fSetStateMethodID, jstr);
106     fPThreadEnv->DeleteLocalRef(jstr);
107 }
108 
postMessage(const Message & message) const109 void SkiaAndroidApp::postMessage(const Message& message) const {
110     SkDEBUGCODE(auto writeSize =) write(fPipes[1], &message, sizeof(message));
111     SkASSERT(writeSize == sizeof(message));
112 }
113 
readMessage(Message * message) const114 void SkiaAndroidApp::readMessage(Message* message) const {
115     SkDEBUGCODE(auto readSize =) read(fPipes[0], message, sizeof(Message));
116     SkASSERT(readSize == sizeof(Message));
117 }
118 
message_callback(int fd,int events,void * data)119 int SkiaAndroidApp::message_callback(int fd, int events, void* data) {
120     auto skiaAndroidApp = (SkiaAndroidApp*)data;
121     Message message;
122     skiaAndroidApp->readMessage(&message);
123     SkASSERT(message.fType != kUndefined);
124 
125     switch (message.fType) {
126         case kDestroyApp: {
127             delete skiaAndroidApp;
128             pthread_exit(nullptr);
129         }
130         case kContentInvalidated: {
131             ((Window_android*)skiaAndroidApp->fWindow)->paintIfNeeded();
132             break;
133         }
134         case kSurfaceCreated: {
135             SkASSERT(!skiaAndroidApp->fNativeWindow && message.fNativeWindow);
136             skiaAndroidApp->fNativeWindow = message.fNativeWindow;
137             auto window_android = (Window_android*)skiaAndroidApp->fWindow;
138             window_android->initDisplay(skiaAndroidApp->fNativeWindow);
139             ((Window_android*)skiaAndroidApp->fWindow)->paintIfNeeded();
140             break;
141         }
142         case kSurfaceChanged: {
143             SkASSERT(message.fNativeWindow);
144             int width = ANativeWindow_getWidth(skiaAndroidApp->fNativeWindow);
145             int height = ANativeWindow_getHeight(skiaAndroidApp->fNativeWindow);
146             auto window_android = (Window_android*)skiaAndroidApp->fWindow;
147             if (message.fNativeWindow != skiaAndroidApp->fNativeWindow) {
148                 window_android->onDisplayDestroyed();
149                 ANativeWindow_release(skiaAndroidApp->fNativeWindow);
150                 skiaAndroidApp->fNativeWindow = message.fNativeWindow;
151                 window_android->initDisplay(skiaAndroidApp->fNativeWindow);
152             }
153             window_android->onResize(width, height);
154             window_android->paintIfNeeded();
155             break;
156         }
157         case kSurfaceDestroyed: {
158             if (skiaAndroidApp->fNativeWindow) {
159                 auto window_android = (Window_android*)skiaAndroidApp->fWindow;
160                 window_android->onDisplayDestroyed();
161                 ANativeWindow_release(skiaAndroidApp->fNativeWindow);
162                 skiaAndroidApp->fNativeWindow = nullptr;
163             }
164             break;
165         }
166         case kKeyPressed: {
167             auto it = ANDROID_TO_WINDOW_KEYMAP.find(message.fKeycode);
168             SkASSERT(it != ANDROID_TO_WINDOW_KEYMAP.end());
169             // No modifier is supported so far
170             skiaAndroidApp->fWindow->onKey(it->second, skui::InputState::kDown, skui::ModifierKey::kNone);
171             skiaAndroidApp->fWindow->onKey(it->second, skui::InputState::kUp, skui::ModifierKey::kNone);
172             break;
173         }
174         case kTouched: {
175             auto it = ANDROID_TO_WINDOW_STATEMAP.find(message.fTouchState);
176             if (it != ANDROID_TO_WINDOW_STATEMAP.end()) {
177                 skiaAndroidApp->fWindow->onTouch(message.fTouchOwner, it->second, message.fTouchX,
178                                                  message.fTouchY);
179             } else {
180                 SkDebugf("Unknown Touch State: %d\n", message.fTouchState);
181             }
182             break;
183         }
184         case kUIStateChanged: {
185             skiaAndroidApp->fWindow->onUIStateChanged(*message.stateName, *message.stateValue);
186             delete message.stateName;
187             delete message.stateValue;
188             break;
189         }
190         default: {
191             // do nothing
192         }
193     }
194 
195     return 1;  // continue receiving callbacks
196 }
197 
pthread_main(void * arg)198 void* SkiaAndroidApp::pthread_main(void* arg) {
199     SkDebugf("pthread_main begins");
200 
201     auto skiaAndroidApp = (SkiaAndroidApp*)arg;
202 
203     // Because JNIEnv is thread sensitive, we need AttachCurrentThread to set our fPThreadEnv
204     skiaAndroidApp->fJavaVM->AttachCurrentThread(&(skiaAndroidApp->fPThreadEnv), nullptr);
205     JNIEnv* env = skiaAndroidApp->fPThreadEnv;
206 
207     ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
208     pipe(skiaAndroidApp->fPipes);
209     ALooper_addFd(looper, skiaAndroidApp->fPipes[0], LOOPER_ID_MESSAGEPIPE, ALOOPER_EVENT_INPUT,
210                   message_callback, skiaAndroidApp);
211 
212     jsize argc = env->GetArrayLength(skiaAndroidApp->fArgs);
213     std::unique_ptr<const char*[]> argv = std::unique_ptr<const char*[]>(new const char*[argc+1]);
214     argv[0] = "viewer";
215     for (jsize i = 0; i < argc; ++i) {
216         jobject argBuffer = env->GetObjectArrayElement(skiaAndroidApp->fArgs, i);
217         void* argBytes = env->GetDirectBufferAddress(argBuffer);
218         argv[i+1] = static_cast<const char*>(argBytes);
219     }
220 
221     skiaAndroidApp->fApp = Application::Create(argc+1,
222                                                const_cast<char**>(argv.get()),
223                                                skiaAndroidApp);
224 
225     while (true) {
226         int ident = ALOOPER_POLL_CALLBACK;
227         while (ident == ALOOPER_POLL_CALLBACK) {
228             ident = ALooper_pollOnce(0, nullptr, nullptr, nullptr);
229         }
230 
231         if (ident >= 0) {
232             SkDebugf("Unhandled ALooper_pollOnce ident=%d !", ident);
233         } else {
234             skiaAndroidApp->fApp->onIdle();
235         }
236     }
237 }
238 
239 extern "C"  // extern "C" is needed for JNI (although the method itself is in C++)
240     JNIEXPORT jlong JNICALL
Java_org_skia_viewer_ViewerApplication_createNativeApp(JNIEnv * env,jobject application,jobject assetManager,jobjectArray args)241     Java_org_skia_viewer_ViewerApplication_createNativeApp(JNIEnv* env,
242                                                            jobject application,
243                                                            jobject assetManager,
244                                                            jobjectArray args) {
245     config_resource_mgr(env, assetManager);
246     SkiaAndroidApp* skiaAndroidApp = new SkiaAndroidApp(env, application, args);
247     return (jlong)((size_t)skiaAndroidApp);
248 }
249 
Java_org_skia_viewer_ViewerApplication_destroyNativeApp(JNIEnv * env,jobject application,jlong handle)250 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerApplication_destroyNativeApp(
251     JNIEnv* env, jobject application, jlong handle) {
252     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
253     skiaAndroidApp->postMessage(Message(kDestroyApp));
254 }
255 
Java_org_skia_viewer_ViewerActivity_onSurfaceCreated(JNIEnv * env,jobject activity,jlong handle,jobject surface)256 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceCreated(
257     JNIEnv* env, jobject activity, jlong handle, jobject surface) {
258     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
259     Message message(kSurfaceCreated);
260     message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
261     skiaAndroidApp->postMessage(message);
262 }
263 
Java_org_skia_viewer_ViewerActivity_onSurfaceChanged(JNIEnv * env,jobject activity,jlong handle,jobject surface)264 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceChanged(
265     JNIEnv* env, jobject activity, jlong handle, jobject surface) {
266     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
267     Message message(kSurfaceChanged);
268     message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
269     skiaAndroidApp->postMessage(message);
270 }
271 
Java_org_skia_viewer_ViewerActivity_onSurfaceDestroyed(JNIEnv * env,jobject activity,jlong handle)272 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceDestroyed(
273     JNIEnv* env, jobject activity, jlong handle) {
274     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
275     skiaAndroidApp->postMessage(Message(kSurfaceDestroyed));
276 }
277 
Java_org_skia_viewer_ViewerActivity_onKeyPressed(JNIEnv * env,jobject activity,jlong handle,jint keycode)278 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onKeyPressed(JNIEnv* env,
279                                                                                    jobject activity,
280                                                                                    jlong handle,
281                                                                                    jint keycode) {
282     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
283     Message message(kKeyPressed);
284     message.fKeycode = keycode;
285     skiaAndroidApp->postMessage(message);
286 }
287 
Java_org_skia_viewer_ViewerActivity_onTouched(JNIEnv * env,jobject activity,jlong handle,jint owner,jint state,jfloat x,jfloat y)288 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onTouched(
289     JNIEnv* env, jobject activity, jlong handle, jint owner, jint state, jfloat x, jfloat y) {
290     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
291     Message message(kTouched);
292     message.fTouchOwner = owner;
293     message.fTouchState = state;
294     message.fTouchX = x;
295     message.fTouchY = y;
296     skiaAndroidApp->postMessage(message);
297 }
298 
Java_org_skia_viewer_ViewerActivity_onUIStateChanged(JNIEnv * env,jobject activity,jlong handle,jstring stateName,jstring stateValue)299 extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onUIStateChanged(
300     JNIEnv* env, jobject activity, jlong handle, jstring stateName, jstring stateValue) {
301     auto skiaAndroidApp = (SkiaAndroidApp*)handle;
302     Message message(kUIStateChanged);
303     const char* nameChars = env->GetStringUTFChars(stateName, nullptr);
304     const char* valueChars = env->GetStringUTFChars(stateValue, nullptr);
305     message.stateName = new SkString(nameChars);
306     message.stateValue = new SkString(valueChars);
307     skiaAndroidApp->postMessage(message);
308     env->ReleaseStringUTFChars(stateName, nameChars);
309     env->ReleaseStringUTFChars(stateValue, valueChars);
310 }
311 
312 }  // namespace sk_app
313