#include "util/android/AndroidWindow.h"
#include <pthread.h>
#include <filesystem>
#include <iostream>
#include "common/debug.h"
#include "util/android/third_party/android_native_app_glue.h"
namespace
{
struct android_app *sApp = nullptr;
pthread_mutex_t sInitWindowMutex;
pthread_cond_t sInitWindowCond;
bool sInitWindowDone = false;
JNIEnv *gJni = nullptr;
const int kScreenOrientationLandscape = 0;
const int kScreenOrientationPortrait = 1;
JNIEnv *GetJniEnv()
{
if (gJni)
return gJni;
sApp->activity->vm->AttachCurrentThread(&gJni, NULL);
return gJni;
}
int SetScreenOrientation(struct android_app *app, int orientation)
{
JNIEnv *jni = GetJniEnv();
if (!jni)
{
std::cerr << "Failed to get JNI env for screen rotation";
return JNI_ERR;
}
jclass clazz = jni->GetObjectClass(app->activity->clazz);
jmethodID methodID = jni->GetMethodID(clazz, "setRequestedOrientation", "(I)V");
jni->CallVoidMethod(app->activity->clazz, methodID, orientation);
return 0;
}
}
AndroidWindow::AndroidWindow() {}
AndroidWindow::~AndroidWindow() {}
bool AndroidWindow::initializeImpl(const std::string &name, int width, int height)
{
return resize(width, height);
}
void AndroidWindow::destroy() {}
void AndroidWindow::disableErrorMessageDialog() {}
void AndroidWindow::resetNativeWindow() {}
EGLNativeWindowType AndroidWindow::getNativeWindow() const
{
return sApp->window;
}
EGLNativeDisplayType AndroidWindow::getNativeDisplay() const
{
return EGL_DEFAULT_DISPLAY;
}
void AndroidWindow::messageLoop()
{
}
void AndroidWindow::setMousePosition(int x, int y)
{
UNIMPLEMENTED();
}
bool AndroidWindow::setOrientation(int width, int height)
{
int32_t err = SetScreenOrientation(
sApp, (width > height) ? kScreenOrientationLandscape : kScreenOrientationPortrait);
return err == 0;
}
bool AndroidWindow::setPosition(int x, int y)
{
UNIMPLEMENTED();
return false;
}
bool AndroidWindow::resize(int width, int height)
{
mWidth = width;
mHeight = height;
pthread_mutex_lock(&sInitWindowMutex);
while (!sInitWindowDone)
{
pthread_cond_wait(&sInitWindowCond, &sInitWindowMutex);
}
pthread_mutex_unlock(&sInitWindowMutex);
if (sApp->window == nullptr)
{
FATAL() << "Window is NULL (is screen locked? e.g. SplashScreen in logcat)";
}
int32_t currentFormat = ANativeWindow_getFormat(sApp->window);
if (currentFormat < 0)
{
ERR() << "ANativeWindow_getFormat() failed: " << currentFormat;
currentFormat = 0;
}
int32_t err = ANativeWindow_setBuffersGeometry(sApp->window, mWidth, mHeight, currentFormat);
return err == 0;
}
void AndroidWindow::setVisible(bool isVisible) {}
void AndroidWindow::signalTestEvent()
{
UNIMPLEMENTED();
}
static void onAppCmd(struct android_app *app, int32_t cmd)
{
switch (cmd)
{
case APP_CMD_INIT_WINDOW:
pthread_mutex_lock(&sInitWindowMutex);
sInitWindowDone = true;
pthread_cond_broadcast(&sInitWindowCond);
pthread_mutex_unlock(&sInitWindowMutex);
break;
case APP_CMD_DESTROY:
if (gJni)
{
sApp->activity->vm->DetachCurrentThread();
}
gJni = nullptr;
break;
}
}
static int32_t onInputEvent(struct android_app *app, AInputEvent *event)
{
return 0;
}
static bool validPollResult(int result)
{
return result >= 0 || result == ALOOPER_POLL_CALLBACK;
}
void android_main(struct android_app *app)
{
int events;
struct android_poll_source *source;
sApp = app;
pthread_mutex_init(&sInitWindowMutex, nullptr);
pthread_cond_init(&sInitWindowCond, nullptr);
app->onAppCmd = onAppCmd;
app->onInputEvent = onInputEvent;
while (
validPollResult(ALooper_pollOnce(-1, nullptr, &events, reinterpret_cast<void **>(&source))))
{
if (source != nullptr)
{
source->process(app, source);
if (sApp->destroyRequested)
{
break;
}
}
}
}
std::string AndroidWindow::GetApplicationDirectory()
{
JNIEnv *jni = GetJniEnv();
if (!jni)
{
std::cerr << "GetApplicationDirectory:: Failed to get JNI env";
return "";
}
jclass nativeActivityClass = jni->GetObjectClass(sApp->activity->clazz);
if (!nativeActivityClass)
{
std::cerr << "GetApplicationDirectory: Failed to get ANativeActivity class";
return "";
}
jmethodID getApplicationContextMethod = jni->GetMethodID(
nativeActivityClass, "getApplicationContext", "()Landroid/content/Context;");
if (!getApplicationContextMethod)
{
std::cerr << "GetApplicationDirectory: Failed to find getApplicationContext method";
return "";
}
jobject context = jni->CallObjectMethod(sApp->activity->clazz, getApplicationContextMethod);
if (!context)
{
std::cerr << "GetApplicationDirectory: Failed to get Context object";
return "";
}
jclass contextClass = jni->GetObjectClass(context);
if (!contextClass)
{
std::cerr << "GetApplicationDirectory: Failed to get Context class";
return "";
}
jmethodID getFilesDirMethod = jni->GetMethodID(contextClass, "getFilesDir", "()Ljava/io/File;");
if (!getFilesDirMethod)
{
std::cerr << "GetApplicationDirectory: Failed to find getFilesDir method";
return "";
}
jobject fileObject = jni->CallObjectMethod(context, getFilesDirMethod);
if (!fileObject)
{
std::cerr << "GetApplicationDirectory: Failed to get File object";
return "";
}
jclass fileClass = jni->GetObjectClass(fileObject);
if (!fileClass)
{
std::cerr << "GetApplicationDirectory: Failed to get File class";
return "";
}
jmethodID getAbsolutePathMethod =
jni->GetMethodID(fileClass, "getAbsolutePath", "()Ljava/lang/String;");
if (!getAbsolutePathMethod)
{
std::cerr << "GetApplicationDirectory: Failed to find getAbsolutePath method";
return "";
}
jstring pathString = (jstring)jni->CallObjectMethod(fileObject, getAbsolutePathMethod);
if (!pathString)
{
std::cerr << "GetApplicationDirectory: Failed to get path string";
return "";
}
const char *pathChars = jni->GetStringUTFChars(pathString, nullptr);
std::string filesDirPath(pathChars);
jni->ReleaseStringUTFChars(pathString, pathChars);
std::filesystem::path fullPath(filesDirPath);
return fullPath.parent_path();
}
std::string AndroidWindow::GetExternalStorageDirectory()
{
JNIEnv *jni = GetJniEnv();
if (!jni)
{
std::cerr << "GetExternalStorageDirectory:: Failed to get JNI env";
return "";
}
jclass classEnvironment = jni->FindClass("android/os/Environment");
if (classEnvironment == 0)
{
std::cerr << "GetExternalStorageDirectory: Failed to find Environment";
return "";
}
jmethodID methodIDgetExternalStorageDirectory =
jni->GetStaticMethodID(classEnvironment, "getExternalStorageDirectory", "()Ljava/io/File;");
if (methodIDgetExternalStorageDirectory == 0)
{
std::cerr << "GetExternalStorageDirectory: Failed to get static method";
return "";
}
jobject objectFile =
jni->CallStaticObjectMethod(classEnvironment, methodIDgetExternalStorageDirectory);
jthrowable exception = jni->ExceptionOccurred();
if (exception != 0)
{
jni->ExceptionDescribe();
jni->ExceptionClear();
std::cerr << "GetExternalStorageDirectory: Failed because of exception";
return "";
}
jclass classFile = jni->GetObjectClass(objectFile);
if (classEnvironment == 0)
{
std::cerr << "GetExternalStorageDirectory: Failed to find object class";
return "";
}
jmethodID methodIDgetAbsolutePath =
jni->GetMethodID(classFile, "getAbsolutePath", "()Ljava/lang/String;");
if (methodIDgetAbsolutePath == 0)
{
std::cerr << "GetExternalStorageDirectory: Failed to get method ID";
return "";
}
jstring stringPath =
static_cast<jstring>(jni->CallObjectMethod(objectFile, methodIDgetAbsolutePath));
exception = jni->ExceptionOccurred();
if (exception != 0)
{
jni->ExceptionDescribe();
jni->ExceptionClear();
std::cerr << "GetExternalStorageDirectory: Failed because of exception";
return "";
}
const char *path = jni->GetStringUTFChars(stringPath, nullptr);
return std::string(path) + "/chromium_tests_root";
}
OSWindow *OSWindow::New()
{
return new AndroidWindow();
}