337 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			337 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
//
 | 
						|
// Copyright 2016 The ANGLE Project Authors. All rights reserved.
 | 
						|
// Use of this source code is governed by a BSD-style license that can be
 | 
						|
// found in the LICENSE file.
 | 
						|
//
 | 
						|
 | 
						|
// AndroidWindow.cpp: Implementation of OSWindow for Android
 | 
						|
 | 
						|
#include "util/android/AndroidWindow.h"
 | 
						|
 | 
						|
#include <pthread.h>
 | 
						|
#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;
 | 
						|
 | 
						|
// SCREEN_ORIENTATION_LANDSCAPE and SCREEN_ORIENTATION_PORTRAIT are
 | 
						|
// available from Android API level 1
 | 
						|
// https://developer.android.com/reference/android/app/Activity#setRequestedOrientation(int)
 | 
						|
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)
 | 
						|
{
 | 
						|
    // Use reverse JNI to call the Java entry point that rotates the
 | 
						|
    // display to respect width and height
 | 
						|
    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;
 | 
						|
}
 | 
						|
}  // namespace
 | 
						|
 | 
						|
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 the entire Activity Surface for now
 | 
						|
    // sApp->window is valid only after sInitWindowDone, which is true after initializeImpl()
 | 
						|
    return sApp->window;
 | 
						|
}
 | 
						|
 | 
						|
EGLNativeDisplayType AndroidWindow::getNativeDisplay() const
 | 
						|
{
 | 
						|
    return EGL_DEFAULT_DISPLAY;
 | 
						|
}
 | 
						|
 | 
						|
void AndroidWindow::messageLoop()
 | 
						|
{
 | 
						|
    // TODO: accumulate events in the real message loop of android_main,
 | 
						|
    // and process them here
 | 
						|
}
 | 
						|
 | 
						|
void AndroidWindow::setMousePosition(int x, int y)
 | 
						|
{
 | 
						|
    UNIMPLEMENTED();
 | 
						|
}
 | 
						|
 | 
						|
bool AndroidWindow::setOrientation(int width, int height)
 | 
						|
{
 | 
						|
    // Set tests to run in correct orientation
 | 
						|
    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;
 | 
						|
 | 
						|
    // sApp->window used below is valid only after Activity Surface is created
 | 
						|
    pthread_mutex_lock(&sInitWindowMutex);
 | 
						|
    while (!sInitWindowDone)
 | 
						|
    {
 | 
						|
        pthread_cond_wait(&sInitWindowCond, &sInitWindowMutex);
 | 
						|
    }
 | 
						|
    pthread_mutex_unlock(&sInitWindowMutex);
 | 
						|
 | 
						|
    // TODO: figure out a way to set the format as well,
 | 
						|
    // which is available only after EGLWindow initialization
 | 
						|
    int32_t err = ANativeWindow_setBuffersGeometry(sApp->window, mWidth, mHeight, 0);
 | 
						|
    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;
 | 
						|
 | 
						|
            // TODO: process other commands and pass them to AndroidWindow for handling
 | 
						|
            // TODO: figure out how to handle APP_CMD_PAUSE,
 | 
						|
            // which should immediately halt all the rendering,
 | 
						|
            // since Activity Surface is no longer available.
 | 
						|
            // Currently tests crash when paused, for example, due to device changing orientation
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static int32_t onInputEvent(struct android_app *app, AInputEvent *event)
 | 
						|
{
 | 
						|
    // TODO: Handle input events
 | 
						|
    return 0;  // 0 == not handled
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
    // Event handlers, invoked from source->process()
 | 
						|
    app->onAppCmd     = onAppCmd;
 | 
						|
    app->onInputEvent = onInputEvent;
 | 
						|
 | 
						|
    // Message loop, polling for events indefinitely (due to -1 timeout)
 | 
						|
    // Must be here in order to handle APP_CMD_INIT_WINDOW event,
 | 
						|
    // which occurs after AndroidWindow::initializeImpl(), but before AndroidWindow::messageLoop
 | 
						|
    while (ALooper_pollAll(-1, nullptr, &events, reinterpret_cast<void **>(&source)) >= 0)
 | 
						|
    {
 | 
						|
        if (source != nullptr)
 | 
						|
        {
 | 
						|
            source->process(app, source);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
std::string AndroidWindow::GetExternalStorageDirectory()
 | 
						|
{
 | 
						|
    // Use reverse JNI.
 | 
						|
    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 "";
 | 
						|
    }
 | 
						|
 | 
						|
    // public static File getExternalStorageDirectory ()
 | 
						|
    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 "";
 | 
						|
    }
 | 
						|
 | 
						|
    // Call method on File object to retrieve String object.
 | 
						|
    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));
 | 
						|
 | 
						|
    // TODO(jmadill): Find how to pass the root test directory to ANGLE. http://crbug.com/1097957
 | 
						|
 | 
						|
    // // https://stackoverflow.com/questions/12841240/android-pass-parameter-to-native-activity
 | 
						|
    // jclass clazz = jni->GetObjectClass(sApp->activity->clazz);
 | 
						|
    // if (clazz == 0)
 | 
						|
    // {
 | 
						|
    //     std::cerr << "GetExternalStorageDirectory: Bad activity";
 | 
						|
    //     return "";
 | 
						|
    // }
 | 
						|
 | 
						|
    // jmethodID giid = jni->GetMethodID(clazz, "getIntent", "()Landroid/content/Intent;");
 | 
						|
    // if (giid == 0)
 | 
						|
    // {
 | 
						|
    //     std::cerr << "GetExternalStorageDirectory: Could not find getIntent";
 | 
						|
    //     return "";
 | 
						|
    // }
 | 
						|
 | 
						|
    // jobject intent = jni->CallObjectMethod(sApp->activity->clazz, giid);
 | 
						|
    // if (intent == 0)
 | 
						|
    // {
 | 
						|
    //     std::cerr << "GetExternalStorageDirectory: Error calling getIntent";
 | 
						|
    //     return "";
 | 
						|
    // }
 | 
						|
 | 
						|
    // jclass icl = jni->GetObjectClass(intent);
 | 
						|
    // if (icl == 0)
 | 
						|
    // {
 | 
						|
    //     std::cerr << "GetExternalStorageDirectory: Error getting getIntent class";
 | 
						|
    //     return "";
 | 
						|
    // }
 | 
						|
 | 
						|
    // jmethodID gseid =
 | 
						|
    //     jni->GetMethodID(icl, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;");
 | 
						|
    // if (gseid == 0)
 | 
						|
    // {
 | 
						|
    //     std::cerr << "GetExternalStorageDirectory: Could not find getStringExtra";
 | 
						|
    //     return "";
 | 
						|
    // }
 | 
						|
 | 
						|
    // jstring stringPath = static_cast<jstring>(jni->CallObjectMethod(
 | 
						|
    //     intent, gseid, jni->NewStringUTF("org.chromium.base.test.util.UrlUtils.RootDirectory")));
 | 
						|
    // if (stringPath != 0)
 | 
						|
    // {
 | 
						|
    //     const char *path = jni->GetStringUTFChars(stringPath, nullptr);
 | 
						|
    //     return std::string(path) + "/chromium_tests_root";
 | 
						|
    // }
 | 
						|
 | 
						|
    // jclass environment = jni->FindClass("org/chromium/base/test/util/UrlUtils");
 | 
						|
    // if (environment == 0)
 | 
						|
    // {
 | 
						|
    //     std::cerr << "GetExternalStorageDirectory: Failed to find Environment";
 | 
						|
    //     return "";
 | 
						|
    // }
 | 
						|
 | 
						|
    // jmethodID getDir =
 | 
						|
    //     jni->GetStaticMethodID(environment, "getIsolatedTestRoot", "()Ljava/lang/String;");
 | 
						|
    // if (getDir == 0)
 | 
						|
    // {
 | 
						|
    //     std::cerr << "GetExternalStorageDirectory: Failed to get static method";
 | 
						|
    //     return "";
 | 
						|
    // }
 | 
						|
 | 
						|
    // stringPath = static_cast<jstring>(jni->CallStaticObjectMethod(environment, getDir));
 | 
						|
 | 
						|
    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";
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
OSWindow *OSWindow::New()
 | 
						|
{
 | 
						|
    // There should be only one live instance of AndroidWindow at a time,
 | 
						|
    // as there is only one Activity Surface behind it.
 | 
						|
    // Creating a new AndroidWindow each time works for ANGLETest,
 | 
						|
    // as it destroys an old window before creating a new one.
 | 
						|
    // TODO: use GLSurfaceView to support multiple windows
 | 
						|
    return new AndroidWindow();
 | 
						|
}
 |