• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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