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