• 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 <iostream>
13 
14 #include "common/debug.h"
15 #include "util/android/third_party/android_native_app_glue.h"
16 
17 namespace
18 {
19 struct android_app *sApp = nullptr;
20 pthread_mutex_t sInitWindowMutex;
21 pthread_cond_t sInitWindowCond;
22 bool sInitWindowDone = false;
23 JNIEnv *gJni         = nullptr;
24 
25 // SCREEN_ORIENTATION_LANDSCAPE and SCREEN_ORIENTATION_PORTRAIT are
26 // available from Android API level 1
27 // https://developer.android.com/reference/android/app/Activity#setRequestedOrientation(int)
28 const int kScreenOrientationLandscape = 0;
29 const int kScreenOrientationPortrait  = 1;
30 
GetJniEnv()31 JNIEnv *GetJniEnv()
32 {
33     if (gJni)
34         return gJni;
35 
36     sApp->activity->vm->AttachCurrentThread(&gJni, NULL);
37     return gJni;
38 }
39 
SetScreenOrientation(struct android_app * app,int orientation)40 int SetScreenOrientation(struct android_app *app, int orientation)
41 {
42     // Use reverse JNI to call the Java entry point that rotates the
43     // display to respect width and height
44     JNIEnv *jni = GetJniEnv();
45     if (!jni)
46     {
47         std::cerr << "Failed to get JNI env for screen rotation";
48         return JNI_ERR;
49     }
50 
51     jclass clazz       = jni->GetObjectClass(app->activity->clazz);
52     jmethodID methodID = jni->GetMethodID(clazz, "setRequestedOrientation", "(I)V");
53     jni->CallVoidMethod(app->activity->clazz, methodID, orientation);
54 
55     return 0;
56 }
57 }  // namespace
58 
AndroidWindow()59 AndroidWindow::AndroidWindow() {}
60 
~AndroidWindow()61 AndroidWindow::~AndroidWindow() {}
62 
initializeImpl(const std::string & name,int width,int height)63 bool AndroidWindow::initializeImpl(const std::string &name, int width, int height)
64 {
65     return resize(width, height);
66 }
destroy()67 void AndroidWindow::destroy() {}
68 
disableErrorMessageDialog()69 void AndroidWindow::disableErrorMessageDialog() {}
70 
resetNativeWindow()71 void AndroidWindow::resetNativeWindow() {}
72 
getNativeWindow() const73 EGLNativeWindowType AndroidWindow::getNativeWindow() const
74 {
75     // Return the entire Activity Surface for now
76     // sApp->window is valid only after sInitWindowDone, which is true after initializeImpl()
77     return sApp->window;
78 }
79 
getNativeDisplay() const80 EGLNativeDisplayType AndroidWindow::getNativeDisplay() const
81 {
82     return EGL_DEFAULT_DISPLAY;
83 }
84 
messageLoop()85 void AndroidWindow::messageLoop()
86 {
87     // TODO: accumulate events in the real message loop of android_main,
88     // and process them here
89 }
90 
setMousePosition(int x,int y)91 void AndroidWindow::setMousePosition(int x, int y)
92 {
93     UNIMPLEMENTED();
94 }
95 
setOrientation(int width,int height)96 bool AndroidWindow::setOrientation(int width, int height)
97 {
98     // Set tests to run in correct orientation
99     int32_t err = SetScreenOrientation(
100         sApp, (width > height) ? kScreenOrientationLandscape : kScreenOrientationPortrait);
101 
102     return err == 0;
103 }
setPosition(int x,int y)104 bool AndroidWindow::setPosition(int x, int y)
105 {
106     UNIMPLEMENTED();
107     return false;
108 }
109 
resize(int width,int height)110 bool AndroidWindow::resize(int width, int height)
111 {
112     mWidth  = width;
113     mHeight = height;
114 
115     // sApp->window used below is valid only after Activity Surface is created
116     pthread_mutex_lock(&sInitWindowMutex);
117     while (!sInitWindowDone)
118     {
119         pthread_cond_wait(&sInitWindowCond, &sInitWindowMutex);
120     }
121     pthread_mutex_unlock(&sInitWindowMutex);
122 
123     // TODO: figure out a way to set the format as well,
124     // which is available only after EGLWindow initialization
125     int32_t err = ANativeWindow_setBuffersGeometry(sApp->window, mWidth, mHeight, 0);
126     return err == 0;
127 }
128 
setVisible(bool isVisible)129 void AndroidWindow::setVisible(bool isVisible) {}
130 
signalTestEvent()131 void AndroidWindow::signalTestEvent()
132 {
133     UNIMPLEMENTED();
134 }
135 
onAppCmd(struct android_app * app,int32_t cmd)136 static void onAppCmd(struct android_app *app, int32_t cmd)
137 {
138     switch (cmd)
139     {
140         case APP_CMD_INIT_WINDOW:
141             pthread_mutex_lock(&sInitWindowMutex);
142             sInitWindowDone = true;
143             pthread_cond_broadcast(&sInitWindowCond);
144             pthread_mutex_unlock(&sInitWindowMutex);
145             break;
146         case APP_CMD_DESTROY:
147             if (gJni)
148             {
149                 sApp->activity->vm->DetachCurrentThread();
150             }
151             gJni = nullptr;
152             break;
153 
154             // TODO: process other commands and pass them to AndroidWindow for handling
155             // TODO: figure out how to handle APP_CMD_PAUSE,
156             // which should immediately halt all the rendering,
157             // since Activity Surface is no longer available.
158             // Currently tests crash when paused, for example, due to device changing orientation
159     }
160 }
161 
onInputEvent(struct android_app * app,AInputEvent * event)162 static int32_t onInputEvent(struct android_app *app, AInputEvent *event)
163 {
164     // TODO: Handle input events
165     return 0;  // 0 == not handled
166 }
167 
android_main(struct android_app * app)168 void android_main(struct android_app *app)
169 {
170     int events;
171     struct android_poll_source *source;
172 
173     sApp = app;
174     pthread_mutex_init(&sInitWindowMutex, nullptr);
175     pthread_cond_init(&sInitWindowCond, nullptr);
176 
177     // Event handlers, invoked from source->process()
178     app->onAppCmd     = onAppCmd;
179     app->onInputEvent = onInputEvent;
180 
181     // Message loop, polling for events indefinitely (due to -1 timeout)
182     // Must be here in order to handle APP_CMD_INIT_WINDOW event,
183     // which occurs after AndroidWindow::initializeImpl(), but before AndroidWindow::messageLoop
184     while (ALooper_pollAll(-1, nullptr, &events, reinterpret_cast<void **>(&source)) >= 0)
185     {
186         if (source != nullptr)
187         {
188             source->process(app, source);
189         }
190     }
191 }
192 
193 // static
GetExternalStorageDirectory()194 std::string AndroidWindow::GetExternalStorageDirectory()
195 {
196     // Use reverse JNI.
197     JNIEnv *jni = GetJniEnv();
198     if (!jni)
199     {
200         std::cerr << "GetExternalStorageDirectory:: Failed to get JNI env";
201         return "";
202     }
203 
204     jclass classEnvironment = jni->FindClass("android/os/Environment");
205     if (classEnvironment == 0)
206     {
207         std::cerr << "GetExternalStorageDirectory: Failed to find Environment";
208         return "";
209     }
210 
211     // public static File getExternalStorageDirectory ()
212     jmethodID methodIDgetExternalStorageDirectory =
213         jni->GetStaticMethodID(classEnvironment, "getExternalStorageDirectory", "()Ljava/io/File;");
214     if (methodIDgetExternalStorageDirectory == 0)
215     {
216         std::cerr << "GetExternalStorageDirectory: Failed to get static method";
217         return "";
218     }
219 
220     jobject objectFile =
221         jni->CallStaticObjectMethod(classEnvironment, methodIDgetExternalStorageDirectory);
222     jthrowable exception = jni->ExceptionOccurred();
223     if (exception != 0)
224     {
225         jni->ExceptionDescribe();
226         jni->ExceptionClear();
227         std::cerr << "GetExternalStorageDirectory: Failed because of exception";
228         return "";
229     }
230 
231     // Call method on File object to retrieve String object.
232     jclass classFile = jni->GetObjectClass(objectFile);
233     if (classEnvironment == 0)
234     {
235         std::cerr << "GetExternalStorageDirectory: Failed to find object class";
236         return "";
237     }
238 
239     jmethodID methodIDgetAbsolutePath =
240         jni->GetMethodID(classFile, "getAbsolutePath", "()Ljava/lang/String;");
241     if (methodIDgetAbsolutePath == 0)
242     {
243         std::cerr << "GetExternalStorageDirectory: Failed to get method ID";
244         return "";
245     }
246 
247     jstring stringPath =
248         static_cast<jstring>(jni->CallObjectMethod(objectFile, methodIDgetAbsolutePath));
249 
250     // TODO(jmadill): Find how to pass the root test directory to ANGLE. http://crbug.com/1097957
251 
252     // // https://stackoverflow.com/questions/12841240/android-pass-parameter-to-native-activity
253     // jclass clazz = jni->GetObjectClass(sApp->activity->clazz);
254     // if (clazz == 0)
255     // {
256     //     std::cerr << "GetExternalStorageDirectory: Bad activity";
257     //     return "";
258     // }
259 
260     // jmethodID giid = jni->GetMethodID(clazz, "getIntent", "()Landroid/content/Intent;");
261     // if (giid == 0)
262     // {
263     //     std::cerr << "GetExternalStorageDirectory: Could not find getIntent";
264     //     return "";
265     // }
266 
267     // jobject intent = jni->CallObjectMethod(sApp->activity->clazz, giid);
268     // if (intent == 0)
269     // {
270     //     std::cerr << "GetExternalStorageDirectory: Error calling getIntent";
271     //     return "";
272     // }
273 
274     // jclass icl = jni->GetObjectClass(intent);
275     // if (icl == 0)
276     // {
277     //     std::cerr << "GetExternalStorageDirectory: Error getting getIntent class";
278     //     return "";
279     // }
280 
281     // jmethodID gseid =
282     //     jni->GetMethodID(icl, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;");
283     // if (gseid == 0)
284     // {
285     //     std::cerr << "GetExternalStorageDirectory: Could not find getStringExtra";
286     //     return "";
287     // }
288 
289     // jstring stringPath = static_cast<jstring>(jni->CallObjectMethod(
290     //     intent, gseid, jni->NewStringUTF("org.chromium.base.test.util.UrlUtils.RootDirectory")));
291     // if (stringPath != 0)
292     // {
293     //     const char *path = jni->GetStringUTFChars(stringPath, nullptr);
294     //     return std::string(path) + "/chromium_tests_root";
295     // }
296 
297     // jclass environment = jni->FindClass("org/chromium/base/test/util/UrlUtils");
298     // if (environment == 0)
299     // {
300     //     std::cerr << "GetExternalStorageDirectory: Failed to find Environment";
301     //     return "";
302     // }
303 
304     // jmethodID getDir =
305     //     jni->GetStaticMethodID(environment, "getIsolatedTestRoot", "()Ljava/lang/String;");
306     // if (getDir == 0)
307     // {
308     //     std::cerr << "GetExternalStorageDirectory: Failed to get static method";
309     //     return "";
310     // }
311 
312     // stringPath = static_cast<jstring>(jni->CallStaticObjectMethod(environment, getDir));
313 
314     exception = jni->ExceptionOccurred();
315     if (exception != 0)
316     {
317         jni->ExceptionDescribe();
318         jni->ExceptionClear();
319         std::cerr << "GetExternalStorageDirectory: Failed because of exception";
320         return "";
321     }
322 
323     const char *path = jni->GetStringUTFChars(stringPath, nullptr);
324     return std::string(path) + "/chromium_tests_root";
325 }
326 
327 // static
New()328 OSWindow *OSWindow::New()
329 {
330     // There should be only one live instance of AndroidWindow at a time,
331     // as there is only one Activity Surface behind it.
332     // Creating a new AndroidWindow each time works for ANGLETest,
333     // as it destroys an old window before creating a new one.
334     // TODO: use GLSurfaceView to support multiple windows
335     return new AndroidWindow();
336 }
337