Edit

kc3-lang/angle/util/android/AndroidWindow.cpp

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2021-04-09 15:31:01
    Hash : 147adcfa
    Message : Android: Call getExternalStorageDirectory natively. This changes the call to base.test.util.UrlUtils to a native OS method. We may need to update this when Chromium starts running tests on "R". At that point we'll need a non-base mechanism to pass the right folder to ANGLE. This could be via env vars, debug properties, command-line arguments, or #defines. The prior implementation is left as commented-out code as a reminder to fix later. Also updates WARN() to std::cerr because WARN() was not showing up when testing. Bug: chromium:1097957 Change-Id: I4a84ea007341dbe7fe2184eac3aae0ddca44cc9c Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2818240 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: Yuly Novikov <ynovikov@chromium.org>

  • util/android/AndroidWindow.cpp
  • //
    // 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();
    }