1 //
2 // Copyright 2016 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6
7 // AndroidWindow.cpp: Implementation of OSWindow for Android
8
9 #include "util/android/AndroidWindow.h"
10
11 #include <pthread.h>
12 #include <filesystem>
13 #include <iostream>
14
15 #include "common/debug.h"
16 #include "util/android/third_party/android_native_app_glue.h"
17
18 namespace
19 {
20 struct android_app *sApp = nullptr;
21 pthread_mutex_t sInitWindowMutex;
22 pthread_cond_t sInitWindowCond;
23 bool sInitWindowDone = false;
24 JNIEnv *gJni = nullptr;
25
26 // SCREEN_ORIENTATION_LANDSCAPE and SCREEN_ORIENTATION_PORTRAIT are
27 // available from Android API level 1
28 // https://developer.android.com/reference/android/app/Activity#setRequestedOrientation(int)
29 const int kScreenOrientationLandscape = 0;
30 const int kScreenOrientationPortrait = 1;
31
GetJniEnv()32 JNIEnv *GetJniEnv()
33 {
34 if (gJni)
35 return gJni;
36
37 sApp->activity->vm->AttachCurrentThread(&gJni, NULL);
38 return gJni;
39 }
40
SetScreenOrientation(struct android_app * app,int orientation)41 int SetScreenOrientation(struct android_app *app, int orientation)
42 {
43 // Use reverse JNI to call the Java entry point that rotates the
44 // display to respect width and height
45 JNIEnv *jni = GetJniEnv();
46 if (!jni)
47 {
48 std::cerr << "Failed to get JNI env for screen rotation";
49 return JNI_ERR;
50 }
51
52 jclass clazz = jni->GetObjectClass(app->activity->clazz);
53 jmethodID methodID = jni->GetMethodID(clazz, "setRequestedOrientation", "(I)V");
54 jni->CallVoidMethod(app->activity->clazz, methodID, orientation);
55
56 return 0;
57 }
58 } // namespace
59
AndroidWindow()60 AndroidWindow::AndroidWindow() {}
61
~AndroidWindow()62 AndroidWindow::~AndroidWindow() {}
63
initializeImpl(const std::string & name,int width,int height)64 bool AndroidWindow::initializeImpl(const std::string &name, int width, int height)
65 {
66 return resize(width, height);
67 }
destroy()68 void AndroidWindow::destroy() {}
69
disableErrorMessageDialog()70 void AndroidWindow::disableErrorMessageDialog() {}
71
resetNativeWindow()72 void AndroidWindow::resetNativeWindow() {}
73
getNativeWindow() const74 EGLNativeWindowType AndroidWindow::getNativeWindow() const
75 {
76 // Return the entire Activity Surface for now
77 // sApp->window is valid only after sInitWindowDone, which is true after initializeImpl()
78 return sApp->window;
79 }
80
getNativeDisplay() const81 EGLNativeDisplayType AndroidWindow::getNativeDisplay() const
82 {
83 return EGL_DEFAULT_DISPLAY;
84 }
85
messageLoop()86 void AndroidWindow::messageLoop()
87 {
88 // TODO: accumulate events in the real message loop of android_main,
89 // and process them here
90 }
91
setMousePosition(int x,int y)92 void AndroidWindow::setMousePosition(int x, int y)
93 {
94 UNIMPLEMENTED();
95 }
96
setOrientation(int width,int height)97 bool AndroidWindow::setOrientation(int width, int height)
98 {
99 // Set tests to run in correct orientation
100 int32_t err = SetScreenOrientation(
101 sApp, (width > height) ? kScreenOrientationLandscape : kScreenOrientationPortrait);
102
103 return err == 0;
104 }
setPosition(int x,int y)105 bool AndroidWindow::setPosition(int x, int y)
106 {
107 UNIMPLEMENTED();
108 return false;
109 }
110
resize(int width,int height)111 bool AndroidWindow::resize(int width, int height)
112 {
113 mWidth = width;
114 mHeight = height;
115
116 // sApp->window used below is valid only after Activity Surface is created
117 pthread_mutex_lock(&sInitWindowMutex);
118 while (!sInitWindowDone)
119 {
120 pthread_cond_wait(&sInitWindowCond, &sInitWindowMutex);
121 }
122 pthread_mutex_unlock(&sInitWindowMutex);
123
124 if (sApp->window == nullptr)
125 {
126 // Note: logging isn't initalized yet but this message shows up in logcat.
127 FATAL() << "Window is NULL (is screen locked? e.g. SplashScreen in logcat)";
128 }
129
130 // On some devices acquiring next swapchain image after window format change (without resize)
131 // does not return VK_ERROR_OUT_OF_DATE_KHR. Further rendering into the acquired image and/or
132 // presenting that image may produce undefined results. Try to preserve current window format
133 // to avoid such problem. Note, window format is automatically set after swapchain create based
134 // on the create info imageFormat.
135 int32_t currentFormat = ANativeWindow_getFormat(sApp->window);
136 if (currentFormat < 0)
137 {
138 ERR() << "ANativeWindow_getFormat() failed: " << currentFormat;
139 currentFormat = 0;
140 }
141
142 int32_t err = ANativeWindow_setBuffersGeometry(sApp->window, mWidth, mHeight, currentFormat);
143 return err == 0;
144 }
145
setVisible(bool isVisible)146 void AndroidWindow::setVisible(bool isVisible) {}
147
signalTestEvent()148 void AndroidWindow::signalTestEvent()
149 {
150 UNIMPLEMENTED();
151 }
152
onAppCmd(struct android_app * app,int32_t cmd)153 static void onAppCmd(struct android_app *app, int32_t cmd)
154 {
155 switch (cmd)
156 {
157 case APP_CMD_INIT_WINDOW:
158 pthread_mutex_lock(&sInitWindowMutex);
159 sInitWindowDone = true;
160 pthread_cond_broadcast(&sInitWindowCond);
161 pthread_mutex_unlock(&sInitWindowMutex);
162 break;
163 case APP_CMD_DESTROY:
164 if (gJni)
165 {
166 sApp->activity->vm->DetachCurrentThread();
167 }
168 gJni = nullptr;
169 break;
170
171 // TODO: process other commands and pass them to AndroidWindow for handling
172 // TODO: figure out how to handle APP_CMD_PAUSE,
173 // which should immediately halt all the rendering,
174 // since Activity Surface is no longer available.
175 // Currently tests crash when paused, for example, due to device changing orientation
176 }
177 }
178
onInputEvent(struct android_app * app,AInputEvent * event)179 static int32_t onInputEvent(struct android_app *app, AInputEvent *event)
180 {
181 // TODO: Handle input events
182 return 0; // 0 == not handled
183 }
184
validPollResult(int result)185 static bool validPollResult(int result)
186 {
187 return result >= 0 || result == ALOOPER_POLL_CALLBACK;
188 }
189
android_main(struct android_app * app)190 void android_main(struct android_app *app)
191 {
192 int events;
193 struct android_poll_source *source;
194
195 sApp = app;
196 pthread_mutex_init(&sInitWindowMutex, nullptr);
197 pthread_cond_init(&sInitWindowCond, nullptr);
198
199 // Event handlers, invoked from source->process()
200 app->onAppCmd = onAppCmd;
201 app->onInputEvent = onInputEvent;
202
203 // Message loop, polling for events indefinitely (due to -1 timeout)
204 // Must be here in order to handle APP_CMD_INIT_WINDOW event,
205 // which occurs after AndroidWindow::initializeImpl(), but before AndroidWindow::messageLoop
206 while (
207 validPollResult(ALooper_pollOnce(-1, nullptr, &events, reinterpret_cast<void **>(&source))))
208 {
209 if (source != nullptr)
210 {
211 source->process(app, source);
212 }
213 }
214 }
215
GetApplicationDirectory()216 std::string AndroidWindow::GetApplicationDirectory()
217 {
218 // Use reverse JNI.
219 JNIEnv *jni = GetJniEnv();
220 if (!jni)
221 {
222 std::cerr << "GetApplicationDirectory:: Failed to get JNI env";
223 return "";
224 }
225
226 // Get the ANativeActivity class
227 jclass nativeActivityClass = jni->GetObjectClass(sApp->activity->clazz);
228 if (!nativeActivityClass)
229 {
230 std::cerr << "GetApplicationDirectory: Failed to get ANativeActivity class";
231 return "";
232 }
233
234 // Get the getApplicationContext() method ID
235 jmethodID getApplicationContextMethod = jni->GetMethodID(
236 nativeActivityClass, "getApplicationContext", "()Landroid/content/Context;");
237 if (!getApplicationContextMethod)
238 {
239 std::cerr << "GetApplicationDirectory: Failed to find getApplicationContext method";
240 return "";
241 }
242
243 // Call getApplicationContext() to get the Context object
244 jobject context = jni->CallObjectMethod(sApp->activity->clazz, getApplicationContextMethod);
245 if (!context)
246 {
247 std::cerr << "GetApplicationDirectory: Failed to get Context object";
248 return "";
249 }
250
251 // Get the Context class
252 jclass contextClass = jni->GetObjectClass(context);
253 if (!contextClass)
254 {
255 std::cerr << "GetApplicationDirectory: Failed to get Context class";
256 return "";
257 }
258
259 // Get the getFilesDir() method ID
260 jmethodID getFilesDirMethod = jni->GetMethodID(contextClass, "getFilesDir", "()Ljava/io/File;");
261 if (!getFilesDirMethod)
262 {
263 std::cerr << "GetApplicationDirectory: Failed to find getFilesDir method";
264 return "";
265 }
266
267 // Call getFilesDir() to get the File object
268 jobject fileObject = jni->CallObjectMethod(context, getFilesDirMethod);
269 if (!fileObject)
270 {
271 std::cerr << "GetApplicationDirectory: Failed to get File object";
272 return "";
273 }
274
275 // Get the File class
276 jclass fileClass = jni->GetObjectClass(fileObject);
277 if (!fileClass)
278 {
279 std::cerr << "GetApplicationDirectory: Failed to get File class";
280 return "";
281 }
282
283 // Get the getAbsolutePath() method ID
284 jmethodID getAbsolutePathMethod =
285 jni->GetMethodID(fileClass, "getAbsolutePath", "()Ljava/lang/String;");
286 if (!getAbsolutePathMethod)
287 {
288 std::cerr << "GetApplicationDirectory: Failed to find getAbsolutePath method";
289 return "";
290 }
291
292 // Call getAbsolutePath() to get the path as a jstring
293 jstring pathString = (jstring)jni->CallObjectMethod(fileObject, getAbsolutePathMethod);
294 if (!pathString)
295 {
296 std::cerr << "GetApplicationDirectory: Failed to get path string";
297 return "";
298 }
299
300 // Convert the jstring to a std::string
301 const char *pathChars = jni->GetStringUTFChars(pathString, nullptr);
302 std::string filesDirPath(pathChars);
303 jni->ReleaseStringUTFChars(pathString, pathChars);
304
305 // Return the base directory, stripping "files" essentially
306 std::filesystem::path fullPath(filesDirPath);
307 return fullPath.parent_path();
308 }
309
310 // static
GetExternalStorageDirectory()311 std::string AndroidWindow::GetExternalStorageDirectory()
312 {
313 // Use reverse JNI.
314 JNIEnv *jni = GetJniEnv();
315 if (!jni)
316 {
317 std::cerr << "GetExternalStorageDirectory:: Failed to get JNI env";
318 return "";
319 }
320
321 jclass classEnvironment = jni->FindClass("android/os/Environment");
322 if (classEnvironment == 0)
323 {
324 std::cerr << "GetExternalStorageDirectory: Failed to find Environment";
325 return "";
326 }
327
328 // public static File getExternalStorageDirectory ()
329 jmethodID methodIDgetExternalStorageDirectory =
330 jni->GetStaticMethodID(classEnvironment, "getExternalStorageDirectory", "()Ljava/io/File;");
331 if (methodIDgetExternalStorageDirectory == 0)
332 {
333 std::cerr << "GetExternalStorageDirectory: Failed to get static method";
334 return "";
335 }
336
337 jobject objectFile =
338 jni->CallStaticObjectMethod(classEnvironment, methodIDgetExternalStorageDirectory);
339 jthrowable exception = jni->ExceptionOccurred();
340 if (exception != 0)
341 {
342 jni->ExceptionDescribe();
343 jni->ExceptionClear();
344 std::cerr << "GetExternalStorageDirectory: Failed because of exception";
345 return "";
346 }
347
348 // Call method on File object to retrieve String object.
349 jclass classFile = jni->GetObjectClass(objectFile);
350 if (classEnvironment == 0)
351 {
352 std::cerr << "GetExternalStorageDirectory: Failed to find object class";
353 return "";
354 }
355
356 jmethodID methodIDgetAbsolutePath =
357 jni->GetMethodID(classFile, "getAbsolutePath", "()Ljava/lang/String;");
358 if (methodIDgetAbsolutePath == 0)
359 {
360 std::cerr << "GetExternalStorageDirectory: Failed to get method ID";
361 return "";
362 }
363
364 jstring stringPath =
365 static_cast<jstring>(jni->CallObjectMethod(objectFile, methodIDgetAbsolutePath));
366
367 // TODO(jmadill): Find how to pass the root test directory to ANGLE. http://crbug.com/1097957
368
369 // // https://stackoverflow.com/questions/12841240/android-pass-parameter-to-native-activity
370 // jclass clazz = jni->GetObjectClass(sApp->activity->clazz);
371 // if (clazz == 0)
372 // {
373 // std::cerr << "GetExternalStorageDirectory: Bad activity";
374 // return "";
375 // }
376
377 // jmethodID giid = jni->GetMethodID(clazz, "getIntent", "()Landroid/content/Intent;");
378 // if (giid == 0)
379 // {
380 // std::cerr << "GetExternalStorageDirectory: Could not find getIntent";
381 // return "";
382 // }
383
384 // jobject intent = jni->CallObjectMethod(sApp->activity->clazz, giid);
385 // if (intent == 0)
386 // {
387 // std::cerr << "GetExternalStorageDirectory: Error calling getIntent";
388 // return "";
389 // }
390
391 // jclass icl = jni->GetObjectClass(intent);
392 // if (icl == 0)
393 // {
394 // std::cerr << "GetExternalStorageDirectory: Error getting getIntent class";
395 // return "";
396 // }
397
398 // jmethodID gseid =
399 // jni->GetMethodID(icl, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;");
400 // if (gseid == 0)
401 // {
402 // std::cerr << "GetExternalStorageDirectory: Could not find getStringExtra";
403 // return "";
404 // }
405
406 // jstring stringPath = static_cast<jstring>(jni->CallObjectMethod(
407 // intent, gseid, jni->NewStringUTF("org.chromium.base.test.util.UrlUtils.RootDirectory")));
408 // if (stringPath != 0)
409 // {
410 // const char *path = jni->GetStringUTFChars(stringPath, nullptr);
411 // return std::string(path) + "/chromium_tests_root";
412 // }
413
414 // jclass environment = jni->FindClass("org/chromium/base/test/util/UrlUtils");
415 // if (environment == 0)
416 // {
417 // std::cerr << "GetExternalStorageDirectory: Failed to find Environment";
418 // return "";
419 // }
420
421 // jmethodID getDir =
422 // jni->GetStaticMethodID(environment, "getIsolatedTestRoot", "()Ljava/lang/String;");
423 // if (getDir == 0)
424 // {
425 // std::cerr << "GetExternalStorageDirectory: Failed to get static method";
426 // return "";
427 // }
428
429 // stringPath = static_cast<jstring>(jni->CallStaticObjectMethod(environment, getDir));
430
431 exception = jni->ExceptionOccurred();
432 if (exception != 0)
433 {
434 jni->ExceptionDescribe();
435 jni->ExceptionClear();
436 std::cerr << "GetExternalStorageDirectory: Failed because of exception";
437 return "";
438 }
439
440 const char *path = jni->GetStringUTFChars(stringPath, nullptr);
441 return std::string(path) + "/chromium_tests_root";
442 }
443
444 // static
New()445 OSWindow *OSWindow::New()
446 {
447 // There should be only one live instance of AndroidWindow at a time,
448 // as there is only one Activity Surface behind it.
449 // Creating a new AndroidWindow each time works for ANGLETest,
450 // as it destroys an old window before creating a new one.
451 // TODO: use GLSurfaceView to support multiple windows
452 return new AndroidWindow();
453 }
454