From 0379ebad63ad6afa0fe7adfa5bf21c347acb8a65 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 3 Dec 2017 10:50:34 -0800 Subject: [PATCH 1/7] Updating android toolchain for QML --- .gitignore | 3 + android/app/CMakeLists.txt | 9 +- android/app/build.gradle | 14 +- android/app/src/main/AndroidManifest.xml | 28 +- android/app/src/main/cpp/GoogleVRHelpers.h | 50 - android/app/src/main/cpp/main.cpp | 62 ++ android/app/src/main/cpp/main.qrc | 5 + android/app/src/main/cpp/native-lib.cpp | 73 -- android/app/src/main/cpp/renderer.cpp | 120 --- android/app/src/main/cpp/renderer.h | 21 - android/app/src/main/cpp/simple.qml | 10 + .../qt5/android/bindings/QtActivity.java | 951 ++++++++++++++++++ .../android/bindings/QtActivityLoader.java | 502 +++++++++ .../qt5/android/bindings/QtApplication.java | 168 ++++ .../saintandreas/testapp/MainActivity.java | 93 -- android/build.gradle | 351 ++++++- android/build_recipes.md | 27 + cmake/macros/TargetGlm.cmake | 10 +- 18 files changed, 2082 insertions(+), 415 deletions(-) delete mode 100644 android/app/src/main/cpp/GoogleVRHelpers.h create mode 100644 android/app/src/main/cpp/main.cpp create mode 100644 android/app/src/main/cpp/main.qrc delete mode 100644 android/app/src/main/cpp/native-lib.cpp delete mode 100644 android/app/src/main/cpp/renderer.cpp delete mode 100644 android/app/src/main/cpp/renderer.h create mode 100644 android/app/src/main/cpp/simple.qml create mode 100644 android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java create mode 100644 android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java create mode 100644 android/app/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java delete mode 100644 android/app/src/main/java/org/saintandreas/testapp/MainActivity.java create mode 100644 android/build_recipes.md diff --git a/.gitignore b/.gitignore index 665238e7da..c268e8a4bd 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,9 @@ local.properties android/gradle* android/.gradle android/app/src/main/jniLibs +android/app/libs +android/app/src/main/res/values/libs.xml +android/app/src/main/assets/bundled # VSCode # List taken from Github Global Ignores master@435c4d92 diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt index 3d4516d0bf..de6a09dab5 100644 --- a/android/app/CMakeLists.txt +++ b/android/app/CMakeLists.txt @@ -1,10 +1,7 @@ set(TARGET_NAME native-lib) -setup_hifi_library() -link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics) +setup_hifi_library(Gui Qml Quick) +link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics entities octree) target_link_libraries(native-lib android log m) - target_opengl() -target_googlevr() - - +target_bullet() \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 29b7e4a6cc..bef13facac 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,13 +1,10 @@ apply plugin: 'com.android.application' -ext.RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0' -ext.RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV' -ext.BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : '' android { compileSdkVersion 26 defaultConfig { - applicationId "org.saintandreas.testapp" + applicationId "com.highfidelity.iface" minSdkVersion 24 targetSdkVersion 26 versionCode 1 @@ -56,8 +53,9 @@ android { } dependencies { - compile fileTree(dir: "${project.rootDir}/libraries/jar", include: 'QtAndroid-bundled.jar') - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.google.vr:sdk-audio:1.80.0' - compile 'com.google.vr:sdk-base:1.80.0' + implementation 'com.google.vr:sdk-audio:1.80.0' + implementation 'com.google.vr:sdk-base:1.80.0' + implementation files('libs/QtAndroid-bundled.jar') + implementation files('libs/QtAndroidBearer-bundled.jar') } + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c96ac0ef90..f439a8bbad 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,28 +1,46 @@ - - + - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/cpp/GoogleVRHelpers.h b/android/app/src/main/cpp/GoogleVRHelpers.h deleted file mode 100644 index 10c46b036f..0000000000 --- a/android/app/src/main/cpp/GoogleVRHelpers.h +++ /dev/null @@ -1,50 +0,0 @@ -#include -#include -#include - -namespace googlevr { - - // Convert a GVR matrix to GLM matrix - glm::mat4 toGlm(const gvr::Mat4f &matrix) { - glm::mat4 result; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - result[j][i] = matrix.m[i][j]; - } - } - return result; - } - - // Given a field of view in degrees, compute the corresponding projection -// matrix. - glm::mat4 perspectiveMatrixFromView(const gvr::Rectf& fov, float z_near, float z_far) { - const float x_left = -std::tan(fov.left * M_PI / 180.0f) * z_near; - const float x_right = std::tan(fov.right * M_PI / 180.0f) * z_near; - const float y_bottom = -std::tan(fov.bottom * M_PI / 180.0f) * z_near; - const float y_top = std::tan(fov.top * M_PI / 180.0f) * z_near; - const float Y = (2 * z_near) / (y_top - y_bottom); - const float A = (x_right + x_left) / (x_right - x_left); - const float B = (y_top + y_bottom) / (y_top - y_bottom); - const float C = (z_near + z_far) / (z_near - z_far); - const float D = (2 * z_near * z_far) / (z_near - z_far); - - glm::mat4 result { 0 }; - result[2][0] = A; - result[1][1] = Y; - result[2][1] = B; - result[2][2] = C; - result[3][2] = D; - result[2][3] = -1; - return result; - } - - glm::quat toGlm(const gvr::ControllerQuat& q) { - glm::quat result; - result.w = q.qw; - result.x = q.qx; - result.y = q.qy; - result.z = q.qz; - return result; - } - -} diff --git a/android/app/src/main/cpp/main.cpp b/android/app/src/main/cpp/main.cpp new file mode 100644 index 0000000000..fd3405d125 --- /dev/null +++ b/android/app/src/main/cpp/main.cpp @@ -0,0 +1,62 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +int QtMsgTypeToAndroidPriority(QtMsgType type) { + int priority = ANDROID_LOG_UNKNOWN; + switch (type) { + case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break; + case QtWarningMsg: priority = ANDROID_LOG_WARN; break; + case QtCriticalMsg: priority = ANDROID_LOG_ERROR; break; + case QtFatalMsg: priority = ANDROID_LOG_FATAL; break; + case QtInfoMsg: priority = ANDROID_LOG_INFO; break; + default: break; + } + return priority; +} + +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + __android_log_write(QtMsgTypeToAndroidPriority(type), "Interface", message.toStdString().c_str()); +} + + +int main(int argc, char* argv[]) +{ + qInstallMessageHandler(messageHandler); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + auto physicsEngine = new PhysicsEngine({}); + QGuiApplication app(argc,argv); + app.setOrganizationName("QtProject"); + app.setOrganizationDomain("qt-project.org"); + app.setApplicationName(QFileInfo(app.applicationFilePath()).baseName()); + + auto screen = app.primaryScreen(); + if (screen) { + auto rect = screen->availableGeometry(); + auto size = screen->availableSize(); + auto foo = screen->availableVirtualGeometry(); + auto pixelRatio = screen->devicePixelRatio(); + qDebug() << pixelRatio; + qDebug() << rect.width(); + qDebug() << rect.height(); + } + QQuickView view; + view.connect(view.engine(), &QQmlEngine::quit, &app, &QCoreApplication::quit); + new QQmlFileSelector(view.engine(), &view); + view.setSource(QUrl("qrc:///simple.qml")); + if (view.status() == QQuickView::Error) + return -1; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.show(); + return app.exec(); +} diff --git a/android/app/src/main/cpp/main.qrc b/android/app/src/main/cpp/main.qrc new file mode 100644 index 0000000000..27d7679317 --- /dev/null +++ b/android/app/src/main/cpp/main.qrc @@ -0,0 +1,5 @@ + + + simple.qml + + diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp deleted file mode 100644 index fe21a250de..0000000000 --- a/android/app/src/main/cpp/native-lib.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include - -#include -#include - -#include "renderer.h" - -int QtMsgTypeToAndroidPriority(QtMsgType type) { - int priority = ANDROID_LOG_UNKNOWN; - switch (type) { - case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break; - case QtWarningMsg: priority = ANDROID_LOG_WARN; break; - case QtCriticalMsg: priority = ANDROID_LOG_ERROR; break; - case QtFatalMsg: priority = ANDROID_LOG_FATAL; break; - case QtInfoMsg: priority = ANDROID_LOG_INFO; break; - default: break; - } - return priority; -} - -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - __android_log_write(QtMsgTypeToAndroidPriority(type), "Interface", message.toStdString().c_str()); -} - -static jlong toJni(NativeRenderer *renderer) { - return reinterpret_cast(renderer); -} - -static NativeRenderer *fromJni(jlong renderer) { - return reinterpret_cast(renderer); -} - -#define JNI_METHOD(r, name) JNIEXPORT r JNICALL Java_org_saintandreas_testapp_MainActivity_##name - -extern "C" { - -JNI_METHOD(jlong, nativeCreateRenderer) -(JNIEnv *env, jclass clazz, jobject class_loader, jobject android_context, jlong native_gvr_api) { - qInstallMessageHandler(messageHandler); - return toJni(new NativeRenderer()); -} - -JNI_METHOD(void, nativeDestroyRenderer) -(JNIEnv *env, jclass clazz, jlong renderer) { - delete fromJni(renderer); -} - -JNI_METHOD(void, nativeInitializeGl) -(JNIEnv *env, jobject obj, jlong renderer) { - fromJni(renderer)->InitializeGl(); -} - -JNI_METHOD(void, nativeDrawFrame) -(JNIEnv *env, jobject obj, jlong renderer) { - fromJni(renderer)->DrawFrame(); -} - -JNI_METHOD(void, nativeOnTriggerEvent) -(JNIEnv *env, jobject obj, jlong renderer) { - fromJni(renderer)->OnTriggerEvent(); -} - -JNI_METHOD(void, nativeOnPause) -(JNIEnv *env, jobject obj, jlong renderer) { - fromJni(renderer)->OnPause(); -} - -JNI_METHOD(void, nativeOnResume) -(JNIEnv *env, jobject obj, jlong renderer) { - fromJni(renderer)->OnResume(); -} - -} // extern "C" diff --git a/android/app/src/main/cpp/renderer.cpp b/android/app/src/main/cpp/renderer.cpp deleted file mode 100644 index 3b23b7e187..0000000000 --- a/android/app/src/main/cpp/renderer.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "renderer.h" - -#include -#include - -#include - -#include -#include - -static const char *kSimepleVertexShader = R"glsl(#version 300 es -#extension GL_OVR_multiview2 : enable - -layout(num_views=2) in; - -layout(location = 0) in vec4 a_Position; - -out vec4 v_Color; - -void main() { - v_Color = vec4(a_Position.xyz, 1.0); - gl_Position = vec4(a_Position.xyz, 1.0); -} -)glsl"; - -static const char *kPassthroughFragmentShader = R"glsl(#version 300 es -precision mediump float; -in vec4 v_Color; -out vec4 FragColor; - -void main() { FragColor = v_Color; } -)glsl"; - - -int LoadGLShader(int type, const char *shadercode) { - GLuint result = 0; - std::string shaderError; - static const std::string SHADER_DEFINES; - if (!gl::compileShader(type, shadercode, SHADER_DEFINES, result, shaderError)) { - qWarning() << "QQQ" << __FUNCTION__ << "Shader compile failure" << shaderError.c_str(); - } - return result; -} - -static void CheckGLError(const char* label) { - int gl_error = glGetError(); - if (gl_error != GL_NO_ERROR) { - qWarning("GL error @ %s: %d", label, gl_error); - // Crash immediately to make OpenGL errors obvious. - abort(); - } -} - -// Contains vertex, normal and other data. -namespace triangle { - static std::array TRIANGLE_VERTS {{ - -0.5f, -0.5f, 0.0f, - 0.5f, -0.5f, 0.0f, - 0.0f, 0.5f, 0.0f - }}; -} - - -void NativeRenderer::InitializeGl() { - glDisable(GL_DEPTH_TEST); - glDisable(GL_CULL_FACE); - glDisable(GL_SCISSOR_TEST); - glDisable(GL_BLEND); - - const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kSimepleVertexShader); - const uint32_t fragShader = LoadGLShader(GL_FRAGMENT_SHADER, kPassthroughFragmentShader); - std::string error; - _program = gl::compileProgram({ vertShader, fragShader }, error); - CheckGLError("build program"); - - glGenBuffers(1, &_geometryBuffer); - glBindBuffer(GL_ARRAY_BUFFER, _geometryBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 9, triangle::TRIANGLE_VERTS.data(), GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); - CheckGLError("upload vertices"); - - glGenVertexArrays(1, &_vao); - glBindBuffer(GL_ARRAY_BUFFER, _geometryBuffer); - glBindVertexArray(_vao); - - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - CheckGLError("build vao "); -} - - -void NativeRenderer::DrawFrame() { - auto now = std::chrono::duration_cast( - std::chrono::system_clock::now() - start); - glm::vec3 v; - v.r = (float) (now.count() % 1000) / 1000.0f; - v.g = 1.0f - v.r; - v.b = 1.0f; - - glClearColor(v.r, v.g, v.b, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glUseProgram(_program); - glBindVertexArray(_vao); - glDrawArrays(GL_TRIANGLES, 0, 3); - glBindVertexArray(0); -} - -void NativeRenderer::OnTriggerEvent() { - qDebug() << "QQQ" << __FUNCTION__; -} - -void NativeRenderer::OnPause() { - qDebug() << "QQQ" << __FUNCTION__; -} - -void NativeRenderer::OnResume() { - qDebug() << "QQQ" << __FUNCTION__; -} diff --git a/android/app/src/main/cpp/renderer.h b/android/app/src/main/cpp/renderer.h deleted file mode 100644 index 522f672e3b..0000000000 --- a/android/app/src/main/cpp/renderer.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include -#include - -class NativeRenderer { -public: - void InitializeGl(); - void DrawFrame(); - void OnTriggerEvent(); - void OnPause(); - void OnResume(); - -private: - std::chrono::time_point start { std::chrono::system_clock::now() }; - - uint32_t _geometryBuffer { 0 }; - uint32_t _vao { 0 }; - uint32_t _program { 0 }; -}; diff --git a/android/app/src/main/cpp/simple.qml b/android/app/src/main/cpp/simple.qml new file mode 100644 index 0000000000..38f3c80374 --- /dev/null +++ b/android/app/src/main/cpp/simple.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0 + +Rectangle { + id: window + width: 320 + height: 480 + focus: true + color: "red" + ColorAnimation on color { from: "red"; to: "yellow"; duration: 1000; loops: Animation.Infinite } +} diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java new file mode 100644 index 0000000000..ed55c16cde --- /dev/null +++ b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java @@ -0,0 +1,951 @@ +/* + Copyright (c) 2016, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.qtproject.qt5.android.bindings; + +import android.app.Activity; +import android.app.Dialog; +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources.Theme; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.os.Build; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.ActionMode; +import android.view.ActionMode.Callback; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager.LayoutParams; +import android.view.accessibility.AccessibilityEvent; + +@SuppressWarnings("unused") +public class QtActivity extends Activity { + public String APPLICATION_PARAMETERS = null; + public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1"; + public final String[] QT_ANDROID_THEMES = new String[]{"Theme_Holo_Light"}; + public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme. + private QtActivityLoader m_loader = new QtActivityLoader(this); + + public QtActivity() { + } + + /////////////////////////// forward all notifications //////////////////////////// + /////////////////////////// Super class calls //////////////////////////////////// + /////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE ////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyEvent != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyEvent, event); + } else { + return super.dispatchKeyEvent(event); + } + } + + public boolean super_dispatchKeyEvent(KeyEvent event) { + return super.dispatchKeyEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchPopulateAccessibilityEvent != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchPopulateAccessibilityEvent, event); + } else { + return super.dispatchPopulateAccessibilityEvent(event); + } + } + + public boolean super_dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + return super_dispatchPopulateAccessibilityEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchTouchEvent != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTouchEvent, ev); + } else { + return super.dispatchTouchEvent(ev); + } + } + + public boolean super_dispatchTouchEvent(MotionEvent event) { + return super.dispatchTouchEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean dispatchTrackballEvent(MotionEvent ev) { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchTrackballEvent != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTrackballEvent, ev); + } else { + return super.dispatchTrackballEvent(ev); + } + } + + public boolean super_dispatchTrackballEvent(MotionEvent event) { + return super.dispatchTrackballEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + + if (QtApplication.m_delegateObject != null && QtApplication.onActivityResult != null) { + QtApplication.invokeDelegateMethod(QtApplication.onActivityResult, requestCode, resultCode, data); + return; + } + super.onActivityResult(requestCode, resultCode, data); + } + + public void super_onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + } + //--------------------------------------------------------------------------- + + @Override + protected void onApplyThemeResource(Theme theme, int resid, boolean first) { + if (!QtApplication.invokeDelegate(theme, resid, first).invoked) { + super.onApplyThemeResource(theme, resid, first); + } + } + + public void super_onApplyThemeResource(Theme theme, int resid, boolean first) { + super.onApplyThemeResource(theme, resid, first); + } + //--------------------------------------------------------------------------- + + + @Override + protected void onChildTitleChanged(Activity childActivity, CharSequence title) { + if (!QtApplication.invokeDelegate(childActivity, title).invoked) { + super.onChildTitleChanged(childActivity, title); + } + } + + public void super_onChildTitleChanged(Activity childActivity, CharSequence title) { + super.onChildTitleChanged(childActivity, title); + } + //--------------------------------------------------------------------------- + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (!QtApplication.invokeDelegate(newConfig).invoked) { + super.onConfigurationChanged(newConfig); + } + } + + public void super_onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + //--------------------------------------------------------------------------- + + @Override + public void onContentChanged() { + if (!QtApplication.invokeDelegate().invoked) { + super.onContentChanged(); + } + } + + public void super_onContentChanged() { + super.onContentChanged(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onContextItemSelected(MenuItem item) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); + if (res.invoked) { + return (Boolean) res.methodReturns; + } else { + return super.onContextItemSelected(item); + } + } + + public boolean super_onContextItemSelected(MenuItem item) { + return super.onContextItemSelected(item); + } + //--------------------------------------------------------------------------- + + @Override + public void onContextMenuClosed(Menu menu) { + if (!QtApplication.invokeDelegate(menu).invoked) { + super.onContextMenuClosed(menu); + } + } + + public void super_onContextMenuClosed(Menu menu) { + super.onContextMenuClosed(menu); + } + //--------------------------------------------------------------------------- + + protected void onCreateHook(Bundle savedInstanceState) { + m_loader.APPLICATION_PARAMETERS = APPLICATION_PARAMETERS; + m_loader.ENVIRONMENT_VARIABLES = ENVIRONMENT_VARIABLES; + m_loader.QT_ANDROID_THEMES = QT_ANDROID_THEMES; + m_loader.QT_ANDROID_DEFAULT_THEME = QT_ANDROID_DEFAULT_THEME; + m_loader.onCreate(savedInstanceState); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + onCreateHook(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + if (!QtApplication.invokeDelegate(menu, v, menuInfo).invoked) { + super.onCreateContextMenu(menu, v, menuInfo); + } + } + + public void super_onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + } + //--------------------------------------------------------------------------- + + @Override + public CharSequence onCreateDescription() { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) { + return (CharSequence) res.methodReturns; + } else { + return super.onCreateDescription(); + } + } + + public CharSequence super_onCreateDescription() { + return super.onCreateDescription(); + } + //--------------------------------------------------------------------------- + + @Override + protected Dialog onCreateDialog(int id) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(id); + if (res.invoked) { + return (Dialog) res.methodReturns; + } else { + return super.onCreateDialog(id); + } + } + + public Dialog super_onCreateDialog(int id) { + return super.onCreateDialog(id); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); + if (res.invoked) { + return (Boolean) res.methodReturns; + } else { + return super.onCreateOptionsMenu(menu); + } + } + + public boolean super_onCreateOptionsMenu(Menu menu) { + return super.onCreateOptionsMenu(menu); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); + if (res.invoked) { + return (Boolean) res.methodReturns; + } else { + return super.onCreatePanelMenu(featureId, menu); + } + } + + public boolean super_onCreatePanelMenu(int featureId, Menu menu) { + return super.onCreatePanelMenu(featureId, menu); + } + //--------------------------------------------------------------------------- + + + @Override + public View onCreatePanelView(int featureId) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId); + if (res.invoked) { + return (View) res.methodReturns; + } else { + return super.onCreatePanelView(featureId); + } + } + + public View super_onCreatePanelView(int featureId) { + return super.onCreatePanelView(featureId); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(outBitmap, canvas); + if (res.invoked) { + return (Boolean) res.methodReturns; + } else { + return super.onCreateThumbnail(outBitmap, canvas); + } + } + + public boolean super_onCreateThumbnail(Bitmap outBitmap, Canvas canvas) { + return super.onCreateThumbnail(outBitmap, canvas); + } + //--------------------------------------------------------------------------- + + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(name, context, attrs); + if (res.invoked) { + return (View) res.methodReturns; + } else { + return super.onCreateView(name, context, attrs); + } + } + + public View super_onCreateView(String name, Context context, AttributeSet attrs) { + return super.onCreateView(name, context, attrs); + } + //--------------------------------------------------------------------------- + + @Override + protected void onDestroy() { + super.onDestroy(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyDown, keyCode, event); + } else { + return super.onKeyDown(keyCode, event); + } + } + + public boolean super_onKeyDown(int keyCode, KeyEvent event) { + return super.onKeyDown(keyCode, event); + } + //--------------------------------------------------------------------------- + + + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyMultiple != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyMultiple, keyCode, repeatCount, event); + } else { + return super.onKeyMultiple(keyCode, repeatCount, event); + } + } + + public boolean super_onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + return super.onKeyMultiple(keyCode, repeatCount, event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyUp != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyUp, keyCode, event); + } else { + return super.onKeyUp(keyCode, event); + } + } + + public boolean super_onKeyUp(int keyCode, KeyEvent event) { + return super.onKeyUp(keyCode, event); + } + //--------------------------------------------------------------------------- + + @Override + public void onLowMemory() { + if (!QtApplication.invokeDelegate().invoked) { + super.onLowMemory(); + } + } + //--------------------------------------------------------------------------- + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, item); + if (res.invoked) { + return (Boolean) res.methodReturns; + } else { + return super.onMenuItemSelected(featureId, item); + } + } + + public boolean super_onMenuItemSelected(int featureId, MenuItem item) { + return super.onMenuItemSelected(featureId, item); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onMenuOpened(int featureId, Menu menu) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu); + if (res.invoked) { + return (Boolean) res.methodReturns; + } else { + return super.onMenuOpened(featureId, menu); + } + } + + public boolean super_onMenuOpened(int featureId, Menu menu) { + return super.onMenuOpened(featureId, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onNewIntent(Intent intent) { + if (!QtApplication.invokeDelegate(intent).invoked) { + super.onNewIntent(intent); + } + } + + public void super_onNewIntent(Intent intent) { + super.onNewIntent(intent); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(item); + if (res.invoked) { + return (Boolean) res.methodReturns; + } else { + return super.onOptionsItemSelected(item); + } + } + + public boolean super_onOptionsItemSelected(MenuItem item) { + return super.onOptionsItemSelected(item); + } + //--------------------------------------------------------------------------- + + @Override + public void onOptionsMenuClosed(Menu menu) { + if (!QtApplication.invokeDelegate(menu).invoked) { + super.onOptionsMenuClosed(menu); + } + } + + public void super_onOptionsMenuClosed(Menu menu) { + super.onOptionsMenuClosed(menu); + } + //--------------------------------------------------------------------------- + + @Override + public void onPanelClosed(int featureId, Menu menu) { + if (!QtApplication.invokeDelegate(featureId, menu).invoked) { + super.onPanelClosed(featureId, menu); + } + } + + public void super_onPanelClosed(int featureId, Menu menu) { + super.onPanelClosed(featureId, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPause() { + super.onPause(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + QtApplication.invokeDelegate(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPostResume() { + super.onPostResume(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPrepareDialog(int id, Dialog dialog) { + if (!QtApplication.invokeDelegate(id, dialog).invoked) { + super.onPrepareDialog(id, dialog); + } + } + + public void super_onPrepareDialog(int id, Dialog dialog) { + super.onPrepareDialog(id, dialog); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu); + if (res.invoked) { + return (Boolean) res.methodReturns; + } else { + return super.onPrepareOptionsMenu(menu); + } + } + + public boolean super_onPrepareOptionsMenu(Menu menu) { + return super.onPrepareOptionsMenu(menu); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, view, menu); + if (res.invoked) { + return (Boolean) res.methodReturns; + } else { + return super.onPreparePanel(featureId, view, menu); + } + } + + public boolean super_onPreparePanel(int featureId, View view, Menu menu) { + return super.onPreparePanel(featureId, view, menu); + } + //--------------------------------------------------------------------------- + + @Override + protected void onRestart() { + super.onRestart(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + if (!QtApplication.invokeDelegate(savedInstanceState).invoked) { + super.onRestoreInstanceState(savedInstanceState); + } + } + + public void super_onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + } + //--------------------------------------------------------------------------- + + @Override + protected void onResume() { + super.onResume(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + public Object onRetainNonConfigurationInstance() { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) { + return res.methodReturns; + } else { + return super.onRetainNonConfigurationInstance(); + } + } + + public Object super_onRetainNonConfigurationInstance() { + return super.onRetainNonConfigurationInstance(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onSaveInstanceState(Bundle outState) { + if (!QtApplication.invokeDelegate(outState).invoked) { + super.onSaveInstanceState(outState); + } + } + + public void super_onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + } + //--------------------------------------------------------------------------- + + @Override + public boolean onSearchRequested() { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(); + if (res.invoked) { + return (Boolean) res.methodReturns; + } else { + return super.onSearchRequested(); + } + } + + public boolean super_onSearchRequested() { + return super.onSearchRequested(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onStart() { + super.onStart(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onStop() { + super.onStop(); + QtApplication.invokeDelegate(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onTitleChanged(CharSequence title, int color) { + if (!QtApplication.invokeDelegate(title, color).invoked) { + super.onTitleChanged(title, color); + } + } + + public void super_onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.onTouchEvent != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTouchEvent, event); + } else { + return super.onTouchEvent(event); + } + } + + public boolean super_onTouchEvent(MotionEvent event) { + return super.onTouchEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onTrackballEvent(MotionEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.onTrackballEvent != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTrackballEvent, event); + } else { + return super.onTrackballEvent(event); + } + } + + public boolean super_onTrackballEvent(MotionEvent event) { + return super.onTrackballEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public void onUserInteraction() { + if (!QtApplication.invokeDelegate().invoked) { + super.onUserInteraction(); + } + } + + public void super_onUserInteraction() { + super.onUserInteraction(); + } + //--------------------------------------------------------------------------- + + @Override + protected void onUserLeaveHint() { + if (!QtApplication.invokeDelegate().invoked) { + super.onUserLeaveHint(); + } + } + + public void super_onUserLeaveHint() { + super.onUserLeaveHint(); + } + //--------------------------------------------------------------------------- + + @Override + public void onWindowAttributesChanged(LayoutParams params) { + if (!QtApplication.invokeDelegate(params).invoked) { + super.onWindowAttributesChanged(params); + } + } + + public void super_onWindowAttributesChanged(LayoutParams params) { + super.onWindowAttributesChanged(params); + } + //--------------------------------------------------------------------------- + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + if (!QtApplication.invokeDelegate(hasFocus).invoked) { + super.onWindowFocusChanged(hasFocus); + } + } + + public void super_onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + } + //--------------------------------------------------------------------------- + + //////////////// Activity API 5 ///////////// +//@ANDROID-5 + @Override + public void onAttachedToWindow() { + if (!QtApplication.invokeDelegate().invoked) { + super.onAttachedToWindow(); + } + } + + public void super_onAttachedToWindow() { + super.onAttachedToWindow(); + } + //--------------------------------------------------------------------------- + + @Override + public void onBackPressed() { + if (!QtApplication.invokeDelegate().invoked) { + super.onBackPressed(); + } + } + + public void super_onBackPressed() { + super.onBackPressed(); + } + //--------------------------------------------------------------------------- + + @Override + public void onDetachedFromWindow() { + if (!QtApplication.invokeDelegate().invoked) { + super.onDetachedFromWindow(); + } + } + + public void super_onDetachedFromWindow() { + super.onDetachedFromWindow(); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyLongPress != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyLongPress, keyCode, event); + } else { + return super.onKeyLongPress(keyCode, event); + } + } + + public boolean super_onKeyLongPress(int keyCode, KeyEvent event) { + return super.onKeyLongPress(keyCode, event); + } + //--------------------------------------------------------------------------- +//@ANDROID-5 + + //////////////// Activity API 8 ///////////// +//@ANDROID-8 + @Override + protected Dialog onCreateDialog(int id, Bundle args) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(id, args); + if (res.invoked) { + return (Dialog) res.methodReturns; + } else { + return super.onCreateDialog(id, args); + } + } + + public Dialog super_onCreateDialog(int id, Bundle args) { + return super.onCreateDialog(id, args); + } + //--------------------------------------------------------------------------- + + @Override + protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { + if (!QtApplication.invokeDelegate(id, dialog, args).invoked) { + super.onPrepareDialog(id, dialog, args); + } + } + + public void super_onPrepareDialog(int id, Dialog dialog, Bundle args) { + super.onPrepareDialog(id, dialog, args); + } + //--------------------------------------------------------------------------- +//@ANDROID-8 + //////////////// Activity API 11 ///////////// + + //@ANDROID-11 + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyShortcutEvent != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyShortcutEvent, event); + } else { + return super.dispatchKeyShortcutEvent(event); + } + } + + public boolean super_dispatchKeyShortcutEvent(KeyEvent event) { + return super.dispatchKeyShortcutEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public void onActionModeFinished(ActionMode mode) { + if (!QtApplication.invokeDelegate(mode).invoked) { + super.onActionModeFinished(mode); + } + } + + public void super_onActionModeFinished(ActionMode mode) { + super.onActionModeFinished(mode); + } + //--------------------------------------------------------------------------- + + @Override + public void onActionModeStarted(ActionMode mode) { + if (!QtApplication.invokeDelegate(mode).invoked) { + super.onActionModeStarted(mode); + } + } + + public void super_onActionModeStarted(ActionMode mode) { + super.onActionModeStarted(mode); + } + //--------------------------------------------------------------------------- + + @Override + public void onAttachFragment(Fragment fragment) { + if (!QtApplication.invokeDelegate(fragment).invoked) { + super.onAttachFragment(fragment); + } + } + + public void super_onAttachFragment(Fragment fragment) { + super.onAttachFragment(fragment); + } + //--------------------------------------------------------------------------- + + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(parent, name, context, attrs); + if (res.invoked) { + return (View) res.methodReturns; + } else { + return super.onCreateView(parent, name, context, attrs); + } + } + + public View super_onCreateView(View parent, String name, Context context, + AttributeSet attrs) { + return super.onCreateView(parent, name, context, attrs); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.onKeyShortcut != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyShortcut, keyCode, event); + } else { + return super.onKeyShortcut(keyCode, event); + } + } + + public boolean super_onKeyShortcut(int keyCode, KeyEvent event) { + return super.onKeyShortcut(keyCode, event); + } + //--------------------------------------------------------------------------- + + @Override + public ActionMode onWindowStartingActionMode(Callback callback) { + QtApplication.InvokeResult res = QtApplication.invokeDelegate(callback); + if (res.invoked) { + return (ActionMode) res.methodReturns; + } else { + return super.onWindowStartingActionMode(callback); + } + } + + public ActionMode super_onWindowStartingActionMode(Callback callback) { + return super.onWindowStartingActionMode(callback); + } + //--------------------------------------------------------------------------- +//@ANDROID-11 + //////////////// Activity API 12 ///////////// + + //@ANDROID-12 + @Override + public boolean dispatchGenericMotionEvent(MotionEvent ev) { + if (QtApplication.m_delegateObject != null && QtApplication.dispatchGenericMotionEvent != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchGenericMotionEvent, ev); + } else { + return super.dispatchGenericMotionEvent(ev); + } + } + + public boolean super_dispatchGenericMotionEvent(MotionEvent event) { + return super.dispatchGenericMotionEvent(event); + } + //--------------------------------------------------------------------------- + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + if (QtApplication.m_delegateObject != null && QtApplication.onGenericMotionEvent != null) { + return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onGenericMotionEvent, event); + } else { + return super.onGenericMotionEvent(event); + } + } + + public boolean super_onGenericMotionEvent(MotionEvent event) { + return super.onGenericMotionEvent(event); + } + //--------------------------------------------------------------------------- +//@ANDROID-12 + + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (QtApplication.m_delegateObject != null && QtApplication.onRequestPermissionsResult != null) { + QtApplication.invokeDelegateMethod(QtApplication.onRequestPermissionsResult, requestCode, permissions, grantResults); + return; + } + } +} diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java new file mode 100644 index 0000000000..e3066a3bd9 --- /dev/null +++ b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java @@ -0,0 +1,502 @@ +/* + Copyright (c) 2016, BogDan Vatra + Contact: http://www.qt-project.org/legal + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and Digia. For licensing terms and + conditions see http://qt.digia.com/licensing. For further information + use the contact form at http://qt.digia.com/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.qtproject.qt5.android.bindings; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.os.Bundle; +import android.util.Log; +import android.view.Window; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; + +import dalvik.system.DexClassLoader; + +public class QtActivityLoader { + private static final String DEX_PATH_KEY = "dex.path"; + private static final String LIB_PATH_KEY = "lib.path"; + private static final String NATIVE_LIBRARIES_KEY = "native.libraries"; + private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables"; + private static final String APPLICATION_PARAMETERS_KEY = "application.parameters"; + private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries"; + private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id"; + private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id"; + private static final String MAIN_LIBRARY_KEY = "main.library"; + private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes"; + private static final String EXTRACT_STYLE_KEY = "extract.android.style"; + private static final String EXTRACT_STYLE_MINIMAL_KEY = "extract.android.style.option"; + private static final int BUFFER_SIZE = 1024; + + String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application, + String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1"; + String[] QT_ANDROID_THEMES = null; + String QT_ANDROID_DEFAULT_THEME = null; + + private String[] m_qtLibs = null; // required qt libs + private int m_displayDensity = -1; + private ContextWrapper m_context; + private ComponentInfo m_contextInfo; + private Class m_delegateClass; + + // this function is used to load and start the loader + private void loadApplication(Bundle loaderParams) { + try { + // add all bundled Qt libs to loader params + ArrayList libs = new ArrayList<>(); + + String libName = m_contextInfo.metaData.getString("android.app.lib_name"); + loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function + loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs); + + // load and start QtLoader class + DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files + m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written. + loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists) + m_context.getClassLoader()); // parent loader + + Class loaderClass = classLoader.loadClass(loaderClassName()); // load QtLoader class + Object qtLoader = loaderClass.newInstance(); // create an instance + Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication", + contextClassName(), + ClassLoader.class, + Bundle.class); + if (!(Boolean) prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams)) { + throw new Exception(""); + } + + QtApplication.setQtContextDelegate(m_delegateClass, qtLoader); + + // now load the application library so it's accessible from this class loader + if (libName != null) { + System.loadLibrary(libName); + } + + Method startAppMethod = qtLoader.getClass().getMethod("startApplication"); + if (!(Boolean) startAppMethod.invoke(qtLoader)) { + throw new Exception(""); + } + } catch (Exception e) { + e.printStackTrace(); + AlertDialog errorDialog = new AlertDialog.Builder(m_context).create(); + errorDialog.setMessage("Fatal error, your application can't be started."); + errorDialog.setButton(DialogInterface.BUTTON_NEUTRAL, m_context.getResources().getString(android.R.string.ok), (dialog, which) -> { + finish(); + }); + errorDialog.show(); + } + } + + static private void copyFile(InputStream inputStream, OutputStream outputStream) + throws IOException { + byte[] buffer = new byte[BUFFER_SIZE]; + + int count; + while ((count = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, count); + } + } + + private void copyAsset(String source, String destination) + throws IOException { + // Already exists, we don't have to do anything + File destinationFile = new File(destination); + if (destinationFile.exists()) { + return; + } + + File parentDirectory = destinationFile.getParentFile(); + if (!parentDirectory.exists()) { + parentDirectory.mkdirs(); + } + + destinationFile.createNewFile(); + + AssetManager assetsManager = m_context.getAssets(); + InputStream inputStream = assetsManager.open(source); + OutputStream outputStream = new FileOutputStream(destinationFile); + copyFile(inputStream, outputStream); + + inputStream.close(); + outputStream.close(); + } + + private static void createBundledBinary(String source, String destination) + throws IOException { + // Already exists, we don't have to do anything + File destinationFile = new File(destination); + if (destinationFile.exists()) { + return; + } + + File parentDirectory = destinationFile.getParentFile(); + if (!parentDirectory.exists()) { + parentDirectory.mkdirs(); + } + + destinationFile.createNewFile(); + + InputStream inputStream = new FileInputStream(source); + OutputStream outputStream = new FileOutputStream(destinationFile); + copyFile(inputStream, outputStream); + + inputStream.close(); + outputStream.close(); + } + + private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) { + File versionFile = new File(pluginsPrefix + "cache.version"); + + long cacheVersion = 0; + if (versionFile.exists() && versionFile.canRead()) { + try { + DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile)); + cacheVersion = inputStream.readLong(); + inputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (cacheVersion != packageVersion) { + deleteRecursively(new File(pluginsPrefix)); + return true; + } else { + return false; + } + } + + private void extractBundledPluginsAndImports(String pluginsPrefix) throws IOException { + String libsDir = m_context.getApplicationInfo().nativeLibraryDir + "/"; + long packageVersion = -1; + try { + PackageInfo packageInfo = m_context.getPackageManager().getPackageInfo(m_context.getPackageName(), 0); + packageVersion = packageInfo.lastUpdateTime; + } catch (Exception e) { + e.printStackTrace(); + } + + + if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) { + return; + } + + { + File versionFile = new File(pluginsPrefix + "cache.version"); + + File parentDirectory = versionFile.getParentFile(); + if (!parentDirectory.exists()) { + parentDirectory.mkdirs(); + } + + versionFile.createNewFile(); + + DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile)); + outputStream.writeLong(packageVersion); + outputStream.close(); + } + + { + String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY; + if (m_contextInfo.metaData.containsKey(key)) { + String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key)); + + for (String bundledImportBinary : list) { + String[] split = bundledImportBinary.split(":"); + String sourceFileName = libsDir + split[0]; + String destinationFileName = pluginsPrefix + split[1]; + createBundledBinary(sourceFileName, destinationFileName); + } + } + } + + { + String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY; + if (m_contextInfo.metaData.containsKey(key)) { + String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key)); + + for (String fileName : list) { + String[] split = fileName.split(":"); + String sourceFileName = split[0]; + String destinationFileName = pluginsPrefix + split[1]; + copyAsset(sourceFileName, destinationFileName); + } + } + + } + } + + private void deleteRecursively(File directory) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + deleteRecursively(file); + } else { + file.delete(); + } + } + + directory.delete(); + } + } + + private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) { + File newCache = new File(localPrefix); + if (!newCache.exists()) { + { + File oldPluginsCache = new File(oldLocalPrefix + "plugins/"); + if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) { + deleteRecursively(oldPluginsCache); + } + } + + { + File oldImportsCache = new File(oldLocalPrefix + "imports/"); + if (oldImportsCache.exists() && oldImportsCache.isDirectory()) { + deleteRecursively(oldImportsCache); + } + } + + { + File oldQmlCache = new File(oldLocalPrefix + "qml/"); + if (oldQmlCache.exists() && oldQmlCache.isDirectory()) { + deleteRecursively(oldQmlCache); + } + } + } + } + + public void startApp() { + try { + if (m_contextInfo.metaData.containsKey("android.app.qt_libs_resource_id")) { + int resourceId = m_contextInfo.metaData.getInt("android.app.qt_libs_resource_id"); + m_qtLibs = m_context.getResources().getStringArray(resourceId); + } + ArrayList libraryList = new ArrayList<>(); + String localPrefix = m_context.getApplicationInfo().dataDir + "/"; + String pluginsPrefix = localPrefix + "qt-reserved-files/"; + cleanOldCacheIfNecessary(localPrefix, pluginsPrefix); + extractBundledPluginsAndImports(pluginsPrefix); + + for (String lib : m_qtLibs) { + libraryList.add(localPrefix + "lib/lib" + lib + ".so"); + } + + if (m_contextInfo.metaData.containsKey("android.app.load_local_libs")) { + String[] extraLibs = m_contextInfo.metaData.getString("android.app.load_local_libs").split(":"); + for (String lib : extraLibs) { + if (lib.length() > 0) { + if (lib.startsWith("lib/")) { + libraryList.add(localPrefix + lib); + } else { + libraryList.add(pluginsPrefix + lib); + } + } + } + } + + Bundle loaderParams = new Bundle(); + loaderParams.putString(DEX_PATH_KEY, new String()); + if (m_contextInfo.metaData.containsKey("android.app.static_init_classes")) { + loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY, + m_contextInfo.metaData.getString("android.app.static_init_classes").split(":")); + } + loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList); + String themePath = m_context.getApplicationInfo().dataDir + "/qt-reserved-files/android-style/"; + String stylePath = themePath + m_displayDensity + "/"; + String extractOption = "full"; + if (m_contextInfo.metaData.containsKey("android.app.extract_android_style")) { + extractOption = m_contextInfo.metaData.getString("android.app.extract_android_style"); + if (!extractOption.equals("full") && !extractOption.equals("minimal") && !extractOption.equals("none")) { + Log.e(QtApplication.QtTAG, "Invalid extract_android_style option \"" + extractOption + "\", defaulting to full"); + extractOption = "full"; + } + } + + if (!(new File(stylePath)).exists() && !extractOption.equals("none")) { + loaderParams.putString(EXTRACT_STYLE_KEY, stylePath); + loaderParams.putBoolean(EXTRACT_STYLE_MINIMAL_KEY, extractOption.equals("minimal")); + } + + if (extractOption.equals("full")) { + ENVIRONMENT_VARIABLES += "\tQT_USE_ANDROID_NATIVE_STYLE=1"; + } + + ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEMES_ROOT_PATH=" + themePath; + + loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES + + "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml" + + "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports" + + "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins"); + + String appParams = null; + if (APPLICATION_PARAMETERS != null) { + appParams = APPLICATION_PARAMETERS; + } + + Intent intent = getIntent(); + if (intent != null) { + String parameters = intent.getStringExtra("applicationArguments"); + if (parameters != null) { + if (appParams == null) { + appParams = parameters; + } else { + appParams += '\t' + parameters; + } + } + } + + if (m_contextInfo.metaData.containsKey("android.app.arguments")) { + String parameters = m_contextInfo.metaData.getString("android.app.arguments"); + if (appParams == null) { + appParams = parameters; + } else { + appParams += '\t' + parameters; + } + } + + if (appParams != null) { + loaderParams.putString(APPLICATION_PARAMETERS_KEY, appParams.replace(' ', '\t').trim()); + } + loadApplication(loaderParams); + } catch (Exception e) { + Log.e(QtApplication.QtTAG, "Can't create main activity", e); + } + } + + QtActivity m_activity; + + QtActivityLoader(QtActivity activity) { + m_context = activity; + m_delegateClass = QtActivity.class; + m_activity = activity; + } + + protected String loaderClassName() { + return "org.qtproject.qt5.android.QtActivityDelegate"; + } + + protected Class contextClassName() { + return android.app.Activity.class; + } + + protected void finish() { + m_activity.finish(); + } + + protected String getTitle() { + return (String) m_activity.getTitle(); + } + + protected void runOnUiThread(Runnable run) { + m_activity.runOnUiThread(run); + } + + Intent getIntent() { + return m_activity.getIntent(); + } + + public void onCreate(Bundle savedInstanceState) { + try { + m_contextInfo = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), PackageManager.GET_META_DATA); + int theme = ((ActivityInfo) m_contextInfo).getThemeResource(); + for (Field f : Class.forName("android.R$style").getDeclaredFields()) { + if (f.getInt(null) == theme) { + QT_ANDROID_THEMES = new String[]{f.getName()}; + QT_ANDROID_DEFAULT_THEME = f.getName(); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + finish(); + return; + } + + try { + m_activity.setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + + m_activity.requestWindowFeature(Window.FEATURE_ACTION_BAR); + + if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) { + QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState); + return; + } + + m_displayDensity = m_activity.getResources().getDisplayMetrics().densityDpi; + + ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME + + "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t"; + + if (null == m_activity.getLastNonConfigurationInstance()) { + if (m_contextInfo.metaData.containsKey("android.app.background_running") + && m_contextInfo.metaData.getBoolean("android.app.background_running")) { + ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t"; + } else { + ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t"; + } + + if (m_contextInfo.metaData.containsKey("android.app.auto_screen_scale_factor") + && m_contextInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) { + ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t"; + } + + startApp(); + } + } +} diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java new file mode 100644 index 0000000000..c592a92a52 --- /dev/null +++ b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java @@ -0,0 +1,168 @@ +/* + Copyright (c) 2016, BogDan Vatra + Contact: http://www.qt.io/licensing/ + + Commercial License Usage + Licensees holding valid commercial Qt licenses may use this file in + accordance with the commercial license agreement provided with the + Software or, alternatively, in accordance with the terms contained in + a written agreement between you and The Qt Company. For licensing terms + and conditions see http://www.qt.io/terms-conditions. For further + information use the contact form at http://www.qt.io/contact-us. + + BSD License Usage + Alternatively, this file may be used under the BSD license as follows: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.qtproject.qt5.android.bindings; + +import android.app.Application; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class QtApplication extends Application { + public final static String QtTAG = "Qt"; + public static Object m_delegateObject = null; + public static Map> m_delegateMethods = new HashMap<>(); + public static Method dispatchKeyEvent = null; + public static Method dispatchPopulateAccessibilityEvent = null; + public static Method dispatchTouchEvent = null; + public static Method dispatchTrackballEvent = null; + public static Method onKeyDown = null; + public static Method onKeyMultiple = null; + public static Method onKeyUp = null; + public static Method onTouchEvent = null; + public static Method onTrackballEvent = null; + public static Method onActivityResult = null; + public static Method onCreate = null; + public static Method onKeyLongPress = null; + public static Method dispatchKeyShortcutEvent = null; + public static Method onKeyShortcut = null; + public static Method dispatchGenericMotionEvent = null; + public static Method onGenericMotionEvent = null; + public static Method onRequestPermissionsResult = null; + private static String activityClassName; + + public static void setQtContextDelegate(Class clazz, Object listener) { + m_delegateObject = listener; + activityClassName = clazz.getCanonicalName(); + + ArrayList delegateMethods = new ArrayList<>(); + for (Method m : listener.getClass().getMethods()) { + if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android")) { + delegateMethods.add(m); + } + } + + ArrayList applicationFields = new ArrayList<>(); + for (Field f : QtApplication.class.getFields()) { + if (f.getDeclaringClass().getName().equals(QtApplication.class.getName())) { + applicationFields.add(f); + } + } + + for (Method delegateMethod : delegateMethods) { + try { + clazz.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes()); + if (m_delegateMethods.containsKey(delegateMethod.getName())) { + m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod); + } else { + ArrayList delegateSet = new ArrayList<>(); + delegateSet.add(delegateMethod); + m_delegateMethods.put(delegateMethod.getName(), delegateSet); + } + for (Field applicationField : applicationFields) { + if (applicationField.getName().equals(delegateMethod.getName())) { + try { + applicationField.set(null, delegateMethod); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } catch (Exception e) { + } + } + } + + @Override + public void onTerminate() { + if (m_delegateObject != null && m_delegateMethods.containsKey("onTerminate")) { + invokeDelegateMethod(m_delegateMethods.get("onTerminate").get(0)); + } + super.onTerminate(); + } + + static class InvokeResult { + boolean invoked = false; + Object methodReturns = null; + } + + private static int stackDeep = -1; + + public static InvokeResult invokeDelegate(Object... args) { + InvokeResult result = new InvokeResult(); + if (m_delegateObject == null) { + return result; + } + StackTraceElement[] elements = Thread.currentThread().getStackTrace(); + if (-1 == stackDeep) { + for (int it = 0; it < elements.length; it++) { + if (elements[it].getClassName().equals(activityClassName)) { + stackDeep = it; + break; + } + } + } + if (-1 == stackDeep) { + return result; + } + + final String methodName = elements[stackDeep].getMethodName(); + if (!m_delegateMethods.containsKey(methodName)) { + return result; + } + + for (Method m : m_delegateMethods.get(methodName)) { + if (m.getParameterTypes().length == args.length) { + result.methodReturns = invokeDelegateMethod(m, args); + result.invoked = true; + return result; + } + } + return result; + } + + public static Object invokeDelegateMethod(Method m, Object... args) { + try { + return m.invoke(m_delegateObject, args); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java b/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java deleted file mode 100644 index 65e6c394e7..0000000000 --- a/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.saintandreas.testapp; - -import android.app.Activity; -import android.content.Context; -import android.opengl.GLSurfaceView; -import android.os.Bundle; -import android.view.View; - -import com.google.vr.ndk.base.AndroidCompat; -import com.google.vr.ndk.base.GvrLayout; - -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; - -public class MainActivity extends Activity { - private final static int IMMERSIVE_STICKY_VIEW_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_FULLSCREEN | - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - - static { - System.loadLibrary("gvr"); - System.loadLibrary("native-lib"); - } - - private long nativeRenderer; - private GLSurfaceView surfaceView; - - private native long nativeCreateRenderer(ClassLoader appClassLoader, Context context); - private native void nativeDestroyRenderer(long renderer); - private native void nativeInitializeGl(long renderer); - private native void nativeDrawFrame(long renderer); - private native void nativeOnTriggerEvent(long renderer); - private native void nativeOnPause(long renderer); - private native void nativeOnResume(long renderer); - - class NativeRenderer implements GLSurfaceView.Renderer { - @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { nativeInitializeGl(nativeRenderer); } - @Override public void onSurfaceChanged(GL10 gl, int width, int height) { } - @Override public void onDrawFrame(GL10 gl) { - nativeDrawFrame(nativeRenderer); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setImmersiveSticky(); - getWindow() - .getDecorView() - .setOnSystemUiVisibilityChangeListener((int visibility)->{ - if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { setImmersiveSticky(); } - }); - - nativeRenderer = nativeCreateRenderer( - getClass().getClassLoader(), - getApplicationContext()); - - surfaceView = new GLSurfaceView(this); - surfaceView.setEGLContextClientVersion(3); - surfaceView.setEGLConfigChooser(8, 8, 8, 0, 0, 0); - surfaceView.setPreserveEGLContextOnPause(true); - surfaceView.setRenderer(new NativeRenderer()); - setContentView(surfaceView); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - nativeDestroyRenderer(nativeRenderer); - nativeRenderer = 0; - } - - @Override - protected void onPause() { - surfaceView.queueEvent(()->nativeOnPause(nativeRenderer)); - surfaceView.onPause(); - super.onPause(); - } - - @Override - protected void onResume() { - super.onResume(); - surfaceView.onResume(); - surfaceView.queueEvent(()->nativeOnResume(nativeRenderer)); - } - - private void setImmersiveSticky() { - getWindow().getDecorView().setSystemUiVisibility(IMMERSIVE_STICKY_VIEW_FLAGS); - } -} diff --git a/android/build.gradle b/android/build.gradle index 75b1b7ad4e..4a5e8b71dc 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,13 @@ +import de.undercouch.gradle.tasks.download.Download +import de.undercouch.gradle.tasks.download.Verify +import groovy.io.FileType +import groovy.json.JsonSlurper +import groovy.xml.XmlUtil +import org.apache.tools.ant.taskdefs.condition.Os + +import java.util.regex.Matcher +import java.util.regex.Pattern + buildscript { repositories { jcenter() @@ -10,6 +20,7 @@ buildscript { plugins { id 'de.undercouch.download' version '3.3.0' + id "cz.malohlava" version "1.0.3" } allprojects { @@ -19,29 +30,37 @@ allprojects { } } +task clean(type: Delete) { + delete rootProject.buildDir +} + +ext { + RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0' + RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV' + BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : '' + EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : '' + QT5_DEPS = [ 'Qt5Core', 'Qt5Gui', 'Qt5Network', 'Qt5Qml', 'Qt5Quick', 'Qt5Script', 'Qt5Widgets', 'Qt5OpenGL' ] +} def baseFolder = new File(HIFI_ANDROID_PRECOMPILED) -def jniFolder = new File('app/src/main/jniLibs/arm64-v8a') - -import org.apache.tools.ant.taskdefs.condition.Os - +def appDir = new File(projectDir, 'app') +def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a') def baseUrl = 'https://hifi-public.s3.amazonaws.com/austin/android/' + def qtFile='qt-5.9.3_linux_armv8-libcpp.tgz' def qtChecksum='547da3547d5690144e23d6504c6d6e91' if (Os.isFamily(Os.FAMILY_MAC)) { qtFile = 'qt-5.9.3_osx_armv8-libcpp.tgz' qtChecksum='6fa3e068cfdee863fc909b294a3a0cc6' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { - qtFile = 'qt-5.9.3_win_armv8-libcpp.tgz' - qtChecksum='3a757378a7e9dbbfc662177e0eb46408' + qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz' + qtChecksum='a93d22c0c59aa112fda18c4c6d157d17' } def packages = [ qt: [ file: qtFile, checksum: qtChecksum, - sharedLibFolder: '', - includeLibs: ['lib/*.so', 'plugins/*/*.so'] ], bullet: [ file: 'bullet-2.83_armv8-libcpp.tgz', @@ -51,6 +70,10 @@ def packages = [ file: 'draco_armv8-libcpp.tgz', checksum: '617a80d213a5ec69fbfa21a1f2f738cd' ], + glm: [ + file: 'glm-0.9.8.tgz', + checksum: 'd2b42cee31d2bc17bab6ce69e6b3f30a' + ], gvr: [ file: 'gvrsdk_v1.101.0.tgz', checksum: '57fd02baa069176ba18597a29b6b4fc7' @@ -63,16 +86,147 @@ def packages = [ file: 'polyvox_armv8-libcpp.tgz', checksum: '5c918288741ee754c16aeb12bb46b9e1', sharedLibFolder: 'lib', - includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'] + includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'], ], tbb: [ file: 'tbb-2018_U1_armv8_libcpp.tgz', checksum: '20768f298f53b195e71b414b0ae240c4', sharedLibFolder: 'lib/release', - includeLibs: ['libtbb.so', 'libtbbmalloc.so'] + includeLibs: ['libtbb.so', 'libtbbmalloc.so'], ] ] + +def scribeLocalFile='scribe' + EXEC_SUFFIX + +def scribeFile='scribe_linux_x86_64' +def scribeChecksum='c98678d9726bd8bbf1bab792acf3ff6c' +if (Os.isFamily(Os.FAMILY_MAC)) { + scribeFile = 'scribe_osx_x86_64' + scribeChecksum='a137ad62c1bf7cca739da219544a9a16' +} else if (Os.isFamily(Os.FAMILY_WINDOWS)) { + scribeFile = 'scribe_win32_x86_64.exe' + scribeChecksum='75c2ce9ed45d17de375e3988bfaba816' + +} + +def options = [ + files: new TreeSet(), + features: new HashSet(), + permissions: new HashSet() +] + +def qmlRoot = new File(HIFI_ANDROID_PRECOMPILED, 'qt') + +def captureOutput = { String command -> + def proc = command.execute() + def sout = new StringBuilder(), serr = new StringBuilder() + proc.consumeProcessOutput(sout, serr) + proc.waitForOrKill(10000) + def errorOutput = serr.toString() + if (!errorOutput.isEmpty()) { + throw new GradleException("Command '${command}' failed with error ${errorOutput}") + } + return sout.toString() +} + +def relativize = { File root, File absolute -> + def relativeURI = root.toURI().relativize(absolute.toURI()) + return new File(relativeURI.toString()) +} + +def scanQmlImports = { File qmlRootPath -> + def qmlImportCommandFile = new File(qmlRoot, 'bin/qmlimportscanner' + EXEC_SUFFIX) + if (!qmlImportCommandFile.exists()) { + throw new GradleException('Unable to find required qmlimportscanner executable at ' + qmlImportCommandFile.parent.toString()) + } + + def command = qmlImportCommandFile.absolutePath + + " -rootPath ${qmlRootPath.absolutePath}" + + " -importPath ${qmlRoot.absolutePath}/qml" + + def commandResult = captureOutput(command) + new JsonSlurper().parseText(commandResult).each { + if (!it.containsKey('path')) { + println "Warning: QML import could not be resolved in any of the import paths: ${it.name}" + return + } + def file = new File(it.path) + // Ignore non-existent files + if (!file.exists()) { + return + } + // Ignore files in the import path + if (file.canonicalPath.startsWith(qmlRootPath.canonicalPath)) { + return + } + if (file.isFile()) { + options.files.add(file) + } else { + file.eachFileRecurse(FileType.FILES, { + options.files.add(it) + }) + } + } +} + +def parseQtDependencies = { List qtLibs -> + qtLibs.each({ + def libFile = new File(qmlRoot, "lib/lib${it}.so") + options.files.add(libFile) + + def androidDeps = new File(qmlRoot, "lib/${it}-android-dependencies.xml") + if (!libFile.exists()) return + if (!androidDeps.exists()) return + + new XmlSlurper().parse(androidDeps).dependencies.lib.depends.'*'.each{ node -> + switch (node.name()) { + case 'lib': + case 'bundled': + def relativeFilename = node.@file.toString() + + // Special case, since this is handled by qmlimportscanner instead + if (relativeFilename.startsWith('qml')) + return + + def file = new File(qmlRoot, relativeFilename) + + if (!file.exists()) + return + + if (file.isFile()) { + options.files.add(file) + } else { + file.eachFileRecurse(FileType.FILES, { options.files.add(it) }) + } + break + + + case 'jar': + if (node.@bundling == "1") { + def jar = new File(qmlRoot, node.@file.toString()) + if (!jar.exists()) { + throw new GradleException('Unable to find required JAR ' + jar.path) + } + options.files.add(jar) + } + break + + case 'permission': + options.permissions.add(node.@name) + break + + case 'feature': + options.features.add(node.@name) + break + + default: + throw new GradleException('Unhandled Android Dependency node ' + node.name()) + } + } + }) +} + task downloadDependencies { doLast { packages.each { entry -> @@ -87,8 +241,6 @@ task downloadDependencies { } } -import de.undercouch.gradle.tasks.download.Verify - task verifyQt(type: Verify) { def p = packages['qt']; src new File(baseFolder, p['file']); checksum p['checksum']; } task verifyBullet(type: Verify) { def p = packages['bullet']; src new File(baseFolder, p['file']); checksum p['checksum'] } task verifyDraco(type: Verify) { def p = packages['draco']; src new File(baseFolder, p['file']); checksum p['checksum'] } @@ -109,19 +261,26 @@ verifyDependencyDownloads.dependsOn verifyTBB task extractDependencies(dependsOn: verifyDependencyDownloads) { doLast { packages.each { entry -> - def folder = entry.key; - def filename = entry.value['file']; + def folder = entry.key + def filename = entry.value['file'] def localFile = new File(HIFI_ANDROID_PRECOMPILED, filename) def localFolder = new File(HIFI_ANDROID_PRECOMPILED, folder) + def fileTree; + if (filename.endsWith('zip')) { + fileTree = zipTree(localFile) + } else { + fileTree = tarTree(resources.gzip(localFile)) + } copy { - from tarTree(resources.gzip(localFile)) + from fileTree into localFolder } } } } -task copyDependencies(dependsOn: extractDependencies) { +// Copies the non Qt dependencies. Qt dependencies (primary libraries and plugins) are handled by the qtBundle task +task copyDependenciesImpl { doLast { packages.each { entry -> def packageName = entry.key @@ -132,30 +291,18 @@ task copyDependencies(dependsOn: extractDependencies) { if (currentPackage.containsKey('includeLibs')) { currentPackage['includeLibs'].each { includeSpec -> tree.include includeSpec } } - tree.visit { element -> + tree.visit { element -> if (!element.file.isDirectory()) { + println "Copying " + element.file + " to " + jniFolder copy { from element.file; into jniFolder } - } - } + } + } } } } } -def scribeFile='scribe_linux_x86_64' -def scribeLocalFile='scribe' -def scribeChecksum='c98678d9726bd8bbf1bab792acf3ff6c' -if (Os.isFamily(Os.FAMILY_MAC)) { - scribeFile = 'scribe_osx_x86_64' - scribeChecksum='a137ad62c1bf7cca739da219544a9a16' -} else if (Os.isFamily(Os.FAMILY_WINDOWS)) { - scribeFile = 'scribe_win32_x86_64.exe' - scribeLocalFile = 'scribe.exe' - scribeChecksum='75c2ce9ed45d17de375e3988bfaba816' - -} - -import de.undercouch.gradle.tasks.download.Download +task copyDependencies(dependsOn: [ extractDependencies, copyDependenciesImpl ]) { } task downloadScribe(type: Download) { src baseUrl + scribeFile @@ -203,14 +350,146 @@ task extractGvrBinaries(dependsOn: extractDependencies) { } -task setupDependencies(dependsOn: [setupScribe, copyDependencies, extractGvrBinaries]) { +// Copy required Qt main libraries and required plugins based on the predefined list here +// FIXME eventually we would like to use the readelf functionality to automatically detect dependencies +// from our built applications and use that during the full build process. However doing so would mean +// hooking existing Android build tasks since the output from the qtBundle logic adds JNI libs, asset +// files and resources files and potentially modifies the AndroidManifest.xml +task qtBundle { + doLast { + parseQtDependencies(QT5_DEPS) + scanQmlImports(new File("${appDir}/../../interface/resources/qml/")) + + def libDestinationDirectory = jniFolder + def jarDestinationDirectory = new File(appDir, 'libs') + def assetDestinationDirectory = new File(appDir, 'src/main/assets/bundled'); + def libsXmlFile = new File(appDir, 'src/main/res/values/libs.xml') + def libPrefix = 'lib' + File.separator + def jarPrefix = 'jar' + File.separator + + def xmlParser = new XmlParser() + def libsXmlRoot = xmlParser.parseText('') + def qtLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'qt_libs']) + def bundledLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_lib']) + def bundledAssetsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_assets']) + + options.files.each { + def sourceFile = it + if (!sourceFile.exists()) { + throw new GradleException("Unable to find dependency file " + sourceFile.toString()) + } + + def relativePath = relativize( qmlRoot, sourceFile ).toString() + def destinationFile + if (relativePath.endsWith('.so')) { + def garbledFileName + if (relativePath.startsWith(libPrefix)) { + garbledFileName = relativePath.substring(libPrefix.size()) + Pattern p = ~/lib(Qt5.*).so/ + Matcher m = p.matcher(garbledFileName) + assert m.matches() + def libName = m.group(1) + xmlParser.createNode(qtLibsNode, 'item', [:]).setValue(libName) + } else { + garbledFileName = 'lib' + relativePath.replace(File.separator, '_'[0]) + xmlParser.createNode(bundledLibsNode, 'item', [:]).setValue("${garbledFileName}:${relativePath}".replace(File.separator, '/')) + } + destinationFile = new File(libDestinationDirectory, garbledFileName) + } else if (relativePath.startsWith('jar')) { + destinationFile = new File(jarDestinationDirectory, relativePath.substring(jarPrefix.size())) + } else { + xmlParser.createNode(bundledAssetsNode, 'item', [:]).setValue("bundled/${relativePath}:${relativePath}".replace(File.separator, '/')) + destinationFile = new File(assetDestinationDirectory, relativePath) + } + + copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } + assert destinationFile.exists() && destinationFile.isFile() + } + def xml = XmlUtil.serialize(libsXmlRoot) + new FileWriter(libsXmlFile).withPrintWriter { writer -> + writer.write(xml) + } + } } +task setupDependencies(dependsOn: [setupScribe, copyDependencies, extractGvrBinaries, qtBundle]) { } + task cleanDependencies(type: Delete) { delete HIFI_ANDROID_PRECOMPILED delete 'app/src/main/jniLibs/arm64-v8a' + delete 'app/src/main/assets/bundled' + delete 'app/src/main/res/values/libs.xml' } -task clean(type: Delete) { - delete rootProject.buildDir +/* +// FIXME derive the path from the gradle environment +def toolchain = [ + version: '4.9', + prefix: 'aarch64-linux-android', + // FIXME derive from the host OS + ndkHost: 'windows-x86_64', +] + +def findDependentLibrary = { String name -> + def libFolders = [ + new File(qmlRoot, 'lib'), + new File("${HIFI_ANDROID_PRECOMPILED}/tbb/lib/release"), + new File("${HIFI_ANDROID_PRECOMPILED}/polyvox/lib/Release"), + new File("${HIFI_ANDROID_PRECOMPILED}/polyvox/lib/"), + new File("${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/libraries"), + ] + } + +def readElfBinary = new File(android.ndkDirectory, "/toolchains/${toolchain.prefix}-${toolchain.version}/prebuilt/${toolchain.ndkHost}/bin/${toolchain.prefix}-readelf${EXEC_SUFFIX}") + +def getDependencies = { File elfBinary -> + Set result = [] + Queue pending = new LinkedList<>() + pending.add(elfBinary) + Set scanned = [] + + Pattern p = ~/.*\(NEEDED\).*Shared library: \[(.*\.so)\]/ + while (!pending.isEmpty()) { + File current = pending.remove() + if (scanned.contains(current)) { + continue + } + scanned.add(current) + def command = "${readElfBinary} -d -W ${current.absolutePath}" + captureOutput(command).split('[\r\n]').each { line -> + Matcher m = p.matcher(line) + if (!m.matches()) { + return + } + def libName = m.group(1) + def file = new File(qmlRoot, "lib/${libName}") + if (file.exists()) { + result.add(file) + pending.add(file) + } + } + } + return result +} + +task testElf (dependsOn: 'externalNativeBuildDebug') { + doLast { + def appLibraries = new HashSet() + def qtDependencies = new HashSet() + externalNativeBuildDebug.nativeBuildConfigurationsJsons.each { File file -> + def json = new JsonSlurper().parse(file) + json.libraries.each { node -> + def outputFile = new File(node.value.output) + if (outputFile.canonicalPath.startsWith(projectDir.canonicalPath)) { + appLibraries.add(outputFile) + } + } + } + + appLibraries.each { File file -> + println getDependencies(file) + } + } +} +*/ \ No newline at end of file diff --git a/android/build_recipes.md b/android/build_recipes.md new file mode 100644 index 0000000000..3bd4310a25 --- /dev/null +++ b/android/build_recipes.md @@ -0,0 +1,27 @@ +## Qt + +### Windows host + +* Install the Android SDK +* Install the Android NDK +* Install Git for Windows +* Install Strawberry Perl +* Install Java 8 (Do NOT use Java 9, it will fail) +* Install Python 3.6 for Windows +* Open a Git Bash command prompt +* Ensure the following commands are visible in the path with `which ` + * gcc + * javac + * python + * gmake +* If any of them fail, fix your path and restart the bash prompt +* Download the Qt sources + * `git clone git://code.qt.io/qt/qt5.git` + * `cd qt5` + * `perl init-repository` + * `git checkout v5.9.3` + * `git submodule update --recursive` + * `cd ..` +* Create a build directory with the command `mkdir qt5build` +* Configure the Qt5 build with the command `../qt5/configure -xplatform android-clang -android-ndk-host windows-x86_64 -confirm-license -opensource --disable-rpath -nomake tests -nomake examples -skip qttranslations -skip qtserialport -skip qt3d -skip qtwebengine -skip qtlocation -skip qtwayland -skip qtsensors -skip qtgamepad -skip qtgamepad -skip qtspeech -skip qtcharts -skip qtx11extras -skip qtmacextras -skip qtvirtualkeyboard -skip qtpurchasing -skip qtdatavis3d -android-ndk C:/Android/NDK -android-toolchain-version 4.9 -android-arch arm64-v8a -no-warnings-are-errors -android-ndk-platform android-24 -v -platform win32-g++ -prefix C:/qt5build_debug -android-sdk C:/Android/SDK ` + \ No newline at end of file diff --git a/cmake/macros/TargetGlm.cmake b/cmake/macros/TargetGlm.cmake index 324cb1c17a..b58e6ba177 100644 --- a/cmake/macros/TargetGlm.cmake +++ b/cmake/macros/TargetGlm.cmake @@ -6,7 +6,11 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_GLM) - add_dependency_external_projects(glm) - find_package(GLM REQUIRED) - target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) + if (ANDROID) + set(GLM_INCLUDE_DIRS "${HIFI_ANDROID_PRECOMPILED}/glm/include") + else() + add_dependency_external_projects(glm) + find_package(GLM REQUIRED) + endif() + target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) endmacro() \ No newline at end of file From 9df9cf7a47653fee165ba2fbb5fb6207e2aa714f Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Thu, 7 Dec 2017 17:02:04 -0800 Subject: [PATCH 2/7] Adding more libs, ifdefs for android incompatible code --- CMakeLists.txt | 11 ++ android/app/CMakeLists.txt | 3 +- android/app/build.gradle | 3 +- android/app/src/main/AndroidManifest.xml | 4 +- android/app/src/main/cpp/+android/simple.qml | 19 +++ android/app/src/main/cpp/main.cpp | 137 +++++++++++++++--- android/app/src/main/cpp/main.qrc | 1 + .../gvrinterface/InterfaceActivity.java | 42 ++++++ android/app/src/main/res/drawable/icon.png | Bin 0 -> 9914 bytes android/build.gradle | 31 +++- android/build_recipes.md | 64 +++++++- interface/CMakeLists.txt | 8 - libraries/gl/src/gl/Config.h | 8 +- libraries/gl/src/gl/GLHelpers.cpp | 2 +- libraries/gl/src/gl/GLHelpers.h | 5 +- libraries/gl/src/gl/OffscreenGLCanvas.cpp | 27 +++- .../plugins/src/plugins/PluginManager.cpp | 31 ++-- libraries/plugins/src/plugins/PluginManager.h | 2 + .../src/FileScriptingInterface.cpp | 37 +++-- libraries/script-engine/src/ScriptEngine.cpp | 6 +- libraries/ui/CMakeLists.txt | 2 +- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 25 +++- libraries/ui/src/ui/OffscreenQmlSurface.h | 4 +- libraries/ui/src/ui/types/FileTypeProfile.cpp | 2 + libraries/ui/src/ui/types/FileTypeProfile.h | 5 +- .../ui/types/FileTypeRequestInterceptor.cpp | 4 + .../src/ui/types/FileTypeRequestInterceptor.h | 4 + .../ui/src/ui/types/HFWebEngineProfile.cpp | 4 + .../ui/src/ui/types/HFWebEngineProfile.h | 5 +- .../types/HFWebEngineRequestInterceptor.cpp | 4 + .../ui/types/HFWebEngineRequestInterceptor.h | 4 + libraries/ui/src/ui/types/RequestFilters.cpp | 3 + libraries/ui/src/ui/types/RequestFilters.h | 8 +- 33 files changed, 409 insertions(+), 106 deletions(-) create mode 100644 android/app/src/main/cpp/+android/simple.qml create mode 100644 android/app/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java create mode 100644 android/app/src/main/res/drawable/icon.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c10e714a3..e033efa982 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,17 @@ else() option(BUILD_TESTS "Build tests" ON) endif() +if (ANDROID) + set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) +else () + set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) +endif () + +foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS}) + list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}") +endforeach() + + option(BUILD_INSTALLER "Build installer" ON) MESSAGE(STATUS "Build server: " ${BUILD_SERVER}) diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt index de6a09dab5..6df17c3dc7 100644 --- a/android/app/CMakeLists.txt +++ b/android/app/CMakeLists.txt @@ -1,6 +1,7 @@ set(TARGET_NAME native-lib) setup_hifi_library(Gui Qml Quick) -link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics entities octree) +#link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics entities octree audio midi recording controllers script-engine ui) +link_hifi_libraries(shared networking gl ui) target_link_libraries(native-lib android log m) target_opengl() diff --git a/android/app/build.gradle b/android/app/build.gradle index bef13facac..066eb7da3d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -55,7 +55,6 @@ android { dependencies { implementation 'com.google.vr:sdk-audio:1.80.0' implementation 'com.google.vr:sdk-base:1.80.0' - implementation files('libs/QtAndroid-bundled.jar') - implementation files('libs/QtAndroidBearer-bundled.jar') + implementation fileTree(dir: 'libs', include: ['*.jar']) } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f439a8bbad..2160f7b591 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ - + @@ -21,7 +21,7 @@ android:roundIcon="@mipmap/ic_launcher_round"> #include #include -#include +#include +#include +#include + +#include +#include +#include + +#include + +Q_LOGGING_CATEGORY(gpugllogging, "hifi.gl") + +bool checkGLError(const char* name) { + GLenum error = glGetError(); + if (!error) { + return false; + } else { + switch (error) { + case GL_INVALID_ENUM: + qCDebug(gpugllogging) << "GLBackend::" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; + break; + case GL_INVALID_VALUE: + qCDebug(gpugllogging) << "GLBackend" << name << ": A numeric argument is out of range.The offending command is ignored and has no other side effect than to set the error flag"; + break; + case GL_INVALID_OPERATION: + qCDebug(gpugllogging) << "GLBackend" << name << ": The specified operation is not allowed in the current state.The offending command is ignored and has no other side effect than to set the error flag.."; + break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + qCDebug(gpugllogging) << "GLBackend" << name << ": The framebuffer object is not complete.The offending command is ignored and has no other side effect than to set the error flag."; + break; + case GL_OUT_OF_MEMORY: + qCDebug(gpugllogging) << "GLBackend" << name << ": There is not enough memory left to execute the command.The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; + break; + default: + qCDebug(gpugllogging) << "GLBackend" << name << ": Unknown error: " << error; + break; + } + return true; + } +} + +bool checkGLErrorDebug(const char* name) { + return checkGLError(name); +} int QtMsgTypeToAndroidPriority(QtMsgType type) { @@ -28,35 +71,87 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt __android_log_write(QtMsgTypeToAndroidPriority(type), "Interface", message.toStdString().c_str()); } +void qt_gl_set_global_share_context(QOpenGLContext *context); int main(int argc, char* argv[]) { qInstallMessageHandler(messageHandler); - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - - auto physicsEngine = new PhysicsEngine({}); + QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); QGuiApplication app(argc,argv); app.setOrganizationName("QtProject"); app.setOrganizationDomain("qt-project.org"); app.setApplicationName(QFileInfo(app.applicationFilePath()).baseName()); + QtWebView::initialize(); + qputenv("QSG_RENDERER_DEBUG", (QStringList() << "render" << "build" << "change" << "upload" << "roots" << "dump").join(';').toUtf8()); - auto screen = app.primaryScreen(); - if (screen) { - auto rect = screen->availableGeometry(); - auto size = screen->availableSize(); - auto foo = screen->availableVirtualGeometry(); - auto pixelRatio = screen->devicePixelRatio(); - qDebug() << pixelRatio; - qDebug() << rect.width(); - qDebug() << rect.height(); + OffscreenGLCanvas sharedCanvas; + if (!sharedCanvas.create()) { + qFatal("Unable to create primary offscreen context"); } - QQuickView view; - view.connect(view.engine(), &QQmlEngine::quit, &app, &QCoreApplication::quit); - new QQmlFileSelector(view.engine(), &view); - view.setSource(QUrl("qrc:///simple.qml")); - if (view.status() == QQuickView::Error) - return -1; - view.setResizeMode(QQuickView::SizeRootObjectToView); - view.show(); + qt_gl_set_global_share_context(sharedCanvas.getContext()); + + auto globalContext = QOpenGLContext::globalShareContext(); + bool threadedGlRendering = QOpenGLContext::supportsThreadedOpenGL(); + + GLWindow window; + window.create(); + window.setGeometry(qApp->primaryScreen()->availableGeometry()); + window.createContext(globalContext); + if (!window.makeCurrent()) { + qFatal("Unable to make primary window GL context current"); + } + + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + + + + ivec2 offscreenSize { 640, 480 }; + + OffscreenQmlSurface::setSharedContext(sharedCanvas.getContext()); + OffscreenQmlSurface* qmlSurface = new OffscreenQmlSurface(); + qmlSurface->create(); + qmlSurface->resize(fromGlm(offscreenSize)); + qmlSurface->load("qrc:///simple.qml"); + qmlSurface->resume(); + + auto discardLambda = qmlSurface->getDiscardLambda(); + + window.showFullScreen(); + QTimer timer; + timer.setInterval(10); + timer.setSingleShot(false); + OffscreenQmlSurface::TextureAndFence currentTextureAndFence; + timer.connect(&timer, &QTimer::timeout, &app, [&]{ + window.makeCurrent(); + glClearColor(0, 1, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + OffscreenQmlSurface::TextureAndFence newTextureAndFence; + if (qmlSurface->fetchTexture(newTextureAndFence)) { + if (currentTextureAndFence.first) { + discardLambda(currentTextureAndFence.first, currentTextureAndFence.second); + } + currentTextureAndFence = newTextureAndFence; + } + checkGLErrorDebug(__FUNCTION__); + + if (currentTextureAndFence.second) { + glWaitSync((GLsync)currentTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + glDeleteSync((GLsync)currentTextureAndFence.second); + currentTextureAndFence.second = nullptr; + } + + if (currentTextureAndFence.first) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, currentTextureAndFence.first, 0); + glBlitFramebuffer(0, 0, offscreenSize.x, offscreenSize.y, 100, 100, offscreenSize.x + 100, offscreenSize.y + 100, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + } + + window.swapBuffers(); + window.doneCurrent(); + }); + timer.start(); return app.exec(); } diff --git a/android/app/src/main/cpp/main.qrc b/android/app/src/main/cpp/main.qrc index 27d7679317..81cf8ef111 100644 --- a/android/app/src/main/cpp/main.qrc +++ b/android/app/src/main/cpp/main.qrc @@ -1,5 +1,6 @@ simple.qml + +android/simple.qml diff --git a/android/app/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java new file mode 100644 index 0000000000..aad769de70 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java @@ -0,0 +1,42 @@ +// +// InterfaceActivity.java +// gvr-interface/java +// +// Created by Stephen Birarda on 1/26/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +package io.highfidelity.gvrinterface; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; +import android.util.Log; +import org.qtproject.qt5.android.bindings.QtActivity; + +public class InterfaceActivity extends QtActivity { + + public static native void handleHifiURL(String hifiURLString); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + // Get the intent that started this activity in case we have a hifi:// URL to parse + Intent intent = getIntent(); + if (intent.getAction() == Intent.ACTION_VIEW) { + Uri data = intent.getData(); + + if (data.getScheme().equals("hifi")) { + handleHifiURL(data.toString()); + } + } + + } +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable/icon.png b/android/app/src/main/res/drawable/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..70aaf9b4ed60a80081821907d53086beb249eb3b GIT binary patch literal 9914 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 P0B~TR#op}QjIjR)wK5d} literal 0 HcmV?d00001 diff --git a/android/build.gradle b/android/build.gradle index 4a5e8b71dc..8e7f728f98 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -39,7 +39,24 @@ ext { RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV' BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : '' EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : '' - QT5_DEPS = [ 'Qt5Core', 'Qt5Gui', 'Qt5Network', 'Qt5Qml', 'Qt5Quick', 'Qt5Script', 'Qt5Widgets', 'Qt5OpenGL' ] + QT5_DEPS = [ + 'Qt5Core', + 'Qt5Gui', + 'Qt5Multimedia', + 'Qt5Network', + 'Qt5OpenGL', + 'Qt5Qml', + 'Qt5Quick', + 'Qt5Script', + 'Qt5ScriptTools', + 'Qt5WebChannel', + 'Qt5WebSockets', + 'Qt5Widgets', + 'Qt5XmlPatterns', + // Android specific + 'Qt5AndroidExtras', + 'Qt5WebView', + ] } def baseFolder = new File(HIFI_ANDROID_PRECOMPILED) @@ -47,11 +64,11 @@ def appDir = new File(projectDir, 'app') def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a') def baseUrl = 'https://hifi-public.s3.amazonaws.com/austin/android/' -def qtFile='qt-5.9.3_linux_armv8-libcpp.tgz' -def qtChecksum='547da3547d5690144e23d6504c6d6e91' +def qtFile='qt-5.9.3_linux_armv8-libcpp_openssl.tgz' +def qtChecksum='04599670ccca84bd2b15f6915568eb2d' if (Os.isFamily(Os.FAMILY_MAC)) { - qtFile = 'qt-5.9.3_osx_armv8-libcpp.tgz' - qtChecksum='6fa3e068cfdee863fc909b294a3a0cc6' + qtFile = 'qt-5.9.3_osx_armv8-libcpp_openssl.tgz' + qtChecksum='4b02de9d67d6bfb202355a808d2d9c59' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz' qtChecksum='a93d22c0c59aa112fda18c4c6d157d17' @@ -146,6 +163,7 @@ def scanQmlImports = { File qmlRootPath -> " -importPath ${qmlRoot.absolutePath}/qml" def commandResult = captureOutput(command) + println commandResult new JsonSlurper().parseText(commandResult).each { if (!it.containsKey('path')) { println "Warning: QML import could not be resolved in any of the import paths: ${it.name}" @@ -358,7 +376,8 @@ task extractGvrBinaries(dependsOn: extractDependencies) { task qtBundle { doLast { parseQtDependencies(QT5_DEPS) - scanQmlImports(new File("${appDir}/../../interface/resources/qml/")) + //scanQmlImports(new File("${appDir}/../../interface/resources/qml/")) + scanQmlImports(new File("${projectDir}/app/src/main/cpp")) def libDestinationDirectory = jniFolder def jarDestinationDirectory = new File(appDir, 'libs') diff --git a/android/build_recipes.md b/android/build_recipes.md index 3bd4310a25..78ca961f5c 100644 --- a/android/build_recipes.md +++ b/android/build_recipes.md @@ -1,3 +1,36 @@ +Different libraries require different mechanism for building. Some are easiest with the standalone toolchain. Some are easiest with the ndk-build tool. Some can rely on CMake to do the right thing. + +## Setup + +### Android build environment + +You need the Android NDK and SDK. The easiest way to get these is to download Android Studio for your platform and use the SDK manager in Studio to install the items. In particular you will need Android API levels 24 and 26, as well as the NDK, CMake, and LLDB. You should be using NDK version 16 or higher. + +The Studio installation can install the SDK wherver you like. This guide assumes you install it to `$HOME/Android/SDK`. It will install the NDK inside the SDK in a folder named `ndk-bundle` but for convenience we will assume a symlink from `$HOME/Android/NDK` to the actual NDK folder exists + +Additionally, some of the tools require a standalone toolchain in order to build. From the NDK build/tools directory you can execute the following command + +`./make-standalone-toolchain.sh --arch=arm64 --platform=android-24 --install-dir=$HOME/Android/arm64_toolchain` + +This will create the toolchain and install it in `$HOME/Android/arm64_toolchain` + +When doing a build that relies on the toolchain you can execute the following commands to enable it + +``` +target_host=aarch64-linux-android +export PATH=$PATH:$HOME/Android/arm64_toolchain/bin +export AR=$target_host-ar +export AS=$target_host-as +export CC=$target_host-gcc +export CXX=$target_host-g++ +export LD=$target_host-ld +export STRIP=$target_host-strip +export CFLAGS="-fPIE -fPIC" +export LDFLAGS="-pie" +``` + + + ## Qt ### Windows host @@ -15,6 +48,10 @@ * python * gmake * If any of them fail, fix your path and restart the bash prompt +* Fetch the pre-built OpenSSL binaries for Android/iOS from here: https://github.com/leenjewel/openssl_for_ios_and_android/releases + * Grab the latest release of the 1.0.2 series + * Open the archive and extract the `/android/openssl-arm64-v8a` folder + * Download the Qt sources * `git clone git://code.qt.io/qt/qt5.git` * `cd qt5` @@ -23,5 +60,28 @@ * `git submodule update --recursive` * `cd ..` * Create a build directory with the command `mkdir qt5build` -* Configure the Qt5 build with the command `../qt5/configure -xplatform android-clang -android-ndk-host windows-x86_64 -confirm-license -opensource --disable-rpath -nomake tests -nomake examples -skip qttranslations -skip qtserialport -skip qt3d -skip qtwebengine -skip qtlocation -skip qtwayland -skip qtsensors -skip qtgamepad -skip qtgamepad -skip qtspeech -skip qtcharts -skip qtx11extras -skip qtmacextras -skip qtvirtualkeyboard -skip qtpurchasing -skip qtdatavis3d -android-ndk C:/Android/NDK -android-toolchain-version 4.9 -android-arch arm64-v8a -no-warnings-are-errors -android-ndk-platform android-24 -v -platform win32-g++ -prefix C:/qt5build_debug -android-sdk C:/Android/SDK ` - \ No newline at end of file +* Configure the Qt5 build with the command `../qt5/configure -xplatform android-clang -android-ndk-host windows-x86_64 -confirm-license -opensource --disable-rpath -nomake tests -nomake examples -skip qttranslations -skip qtserialport -skip qt3d -skip qtwebengine -skip qtlocation -skip qtwayland -skip qtsensors -skip qtgamepad -skip qtgamepad -skip qtspeech -skip qtcharts -skip qtx11extras -skip qtmacextras -skip qtvirtualkeyboard -skip qtpurchasing -skip qtdatavis3d -android-ndk C:/Android/NDK -android-toolchain-version 4.9 -android-arch arm64-v8a -no-warnings-are-errors -android-ndk-platform android-24 -v -platform win32-g++ -prefix C:/qt5build_debug -android-sdk C:/Android/SDK -c++std c++14 -openssl-linked -L/lib -I/include` + + + +## TBB + +Use the ndk-build tool + +ndk-build tbb tbbmalloc target=android arch=ia32 tbb_os=windows ndk_version=16 + +## OpenSSL + +Use a standalone toolchain + +* Grab the latest 1.1.0x series source from https://github.com/openssl/openssl/releases +* Follow the NDK guidelines for building a standalone toolchain for aarch64 +* Use the following script to configure and build OpenSSL +* Enable the standalone toolchain with the export commands described above +* Configure SSL with the command `./Configure android64-aarch64 no-asm no-ssl2 no-ssl3 no-comp no-hw no-engine --prefix=$HOME/Android/openssl_1.1.0g` +* Build and install SSL with the command `make depend && make && make install` + + + + + diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 4c1b8d8d92..a38d428eba 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -56,14 +56,6 @@ else () list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP}) endif () -if (ANDROID) - set(PLATFORM_QT_COMPONENTS AndroidExtras) - set(PLATFORM_QT_LIBRARIES Qt5::AndroidExtras) -else () - set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) - set(PLATFORM_QT_LIBRARIES Qt5::WebEngine Qt5::WebEngineWidgets) -endif () - find_package( Qt5 COMPONENTS Gui Multimedia Network OpenGL Qml Quick Script Svg diff --git a/libraries/gl/src/gl/Config.h b/libraries/gl/src/gl/Config.h index ff282a1ca0..b1bafe1ba6 100644 --- a/libraries/gl/src/gl/Config.h +++ b/libraries/gl/src/gl/Config.h @@ -14,10 +14,10 @@ #include -#if defined(QT_OPENGL_ES_3_1) -// Minimum GL ES version required is 3.1 +#if defined(Q_OS_ANDROID) +// Minimum GL ES version required is 3.2 #define GL_MIN_VERSION_MAJOR 0x03 -#define GL_MIN_VERSION_MINOR 0x01 +#define GL_MIN_VERSION_MINOR 0x02 #define GL_DEFAULT_VERSION_MAJOR GL_MIN_VERSION_MAJOR #define GL_DEFAULT_VERSION_MINOR GL_MIN_VERSION_MINOR #else @@ -33,7 +33,7 @@ #if defined(Q_OS_ANDROID) #include -#include +#include #define GL_DEPTH_COMPONENT32_OES 0x81A7 #define GL_TIME_ELAPSED_EXT 0x88BF diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index ed0594135a..0e3321d99d 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -28,7 +28,7 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { static QSurfaceFormat format; static std::once_flag once; std::call_once(once, [] { -#if defined(QT_OPENGL_ES_3_1) +#if defined(Q_OS_ANDROID) format.setRenderableType(QSurfaceFormat::OpenGLES); format.setRedBufferSize(8); format.setGreenBufferSize(8); diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index 80fc2c5f70..73319e2e6d 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -25,8 +25,9 @@ class QSurfaceFormat; class QGLFormat; template -#if defined(QT_OPENGL_ES_3_1) -void setGLFormatVersion(F& format, int major = 3, int minor = 1) +// https://bugreports.qt.io/browse/QTBUG-64703 prevents us from using "defined(QT_OPENGL_ES_3_1)" +#if defined(Q_OS_ANDROID) +void setGLFormatVersion(F& format, int major = 3, int minor = 2) #else void setGLFormatVersion(F& format, int major = 4, int minor = 5) #endif diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index b974564705..eb99933101 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -46,15 +46,28 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { _context->setShareContext(sharedContext); } _context->setFormat(getDefaultOpenGLSurfaceFormat()); - - if (_context->create()) { - _offscreenSurface->setFormat(_context->format()); - _offscreenSurface->create(); - return _offscreenSurface->isValid(); + if (!_context->create()) { + qFatal("Failed to create OffscreenGLCanvas context"); } - qWarning("Failed to create OffscreenGLCanvas context"); - return false; + _offscreenSurface->setFormat(_context->format()); + _offscreenSurface->create(); +#if !defined(Q_OS_ANDROID) + if (!_context->makeCurrent(_offscreenSurface)) { + qFatal("Unable to make offscreen surface current"); + } +#else + // For some reason, the offscreen surface is considered invalid on android + // possibly because of a bad format? Would need to add some logging to the + // eglpbuffer implementation used from the android platform plugin. + // Alternatively investigate the use of an invisible surface view to do + // a 'native' offscreen surface + if (!_offscreenSurface->isValid()) { + qFatal("Offscreen surface is invalid"); + } +#endif + + return true; } bool OffscreenGLCanvas::makeCurrent() { diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 18ac905ef1..630520957a 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -177,10 +177,6 @@ const SteamClientPluginPointer PluginManager::getSteamClientPlugin() { return steamClientPlugin; } -#ifndef Q_OS_ANDROID - -static DisplayPluginList displayPlugins; - const DisplayPluginList& PluginManager::getDisplayPlugins() { static std::once_flag once; static auto deviceAddedCallback = [](QString deviceName) { @@ -194,7 +190,7 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { std::call_once(once, [&] { // Grab the built in plugins - displayPlugins = _displayPluginProvider(); + _displayPlugins = _displayPluginProvider(); // Now grab the dynamic plugins @@ -202,11 +198,11 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { DisplayProvider* displayProvider = qobject_cast(loader->instance()); if (displayProvider) { for (auto displayPlugin : displayProvider->getDisplayPlugins()) { - displayPlugins.push_back(displayPlugin); + _displayPlugins.push_back(displayPlugin); } } } - for (auto plugin : displayPlugins) { + for (auto plugin : _displayPlugins) { connect(plugin.get(), &Plugin::deviceConnected, this, deviceAddedCallback, Qt::QueuedConnection); connect(plugin.get(), &Plugin::subdeviceConnected, this, subdeviceAddedCallback, Qt::QueuedConnection); plugin->setContainer(_container); @@ -214,21 +210,17 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { } }); - return displayPlugins; + return _displayPlugins; } void PluginManager::disableDisplayPlugin(const QString& name) { - for (size_t i = 0; i < displayPlugins.size(); ++i) { - if (displayPlugins[i]->getName() == name) { - displayPlugins.erase(displayPlugins.begin() + i); - break; - } - } + std::remove_if(_displayPlugins.begin(), _displayPlugins.end(), [&](const DisplayPluginPointer& plugin){ + return plugin->getName() == name; + }); } const InputPluginList& PluginManager::getInputPlugins() { - static InputPluginList inputPlugins; static std::once_flag once; static auto deviceAddedCallback = [](QString deviceName) { qCDebug(plugins) << "Added device: " << deviceName; @@ -240,7 +232,7 @@ const InputPluginList& PluginManager::getInputPlugins() { }; std::call_once(once, [&] { - inputPlugins = _inputPluginProvider(); + _inputPlugins = _inputPluginProvider(); // Now grab the dynamic plugins for (auto loader : getLoadedPlugins()) { @@ -248,20 +240,20 @@ const InputPluginList& PluginManager::getInputPlugins() { if (inputProvider) { for (auto inputPlugin : inputProvider->getInputPlugins()) { if (inputPlugin->isSupported()) { - inputPlugins.push_back(inputPlugin); + _inputPlugins.push_back(inputPlugin); } } } } - for (auto plugin : inputPlugins) { + for (auto plugin : _inputPlugins) { connect(plugin.get(), &Plugin::deviceConnected, this, deviceAddedCallback, Qt::QueuedConnection); connect(plugin.get(), &Plugin::subdeviceConnected, this, subdeviceAddedCallback, Qt::QueuedConnection); plugin->setContainer(_container); plugin->init(); } }); - return inputPlugins; + return _inputPlugins; } void PluginManager::setPreferredDisplayPlugins(const QStringList& displays) { @@ -328,4 +320,3 @@ void PluginManager::shutdown() { } } } -#endif diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 08fe4fde20..f16ad7d09f 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -44,4 +44,6 @@ private: CodecPluginProvider _codecPluginProvider { []()->CodecPluginList { return {}; } }; InputPluginSettingsPersister _inputSettingsPersister { [](const InputPluginList& list) {} }; PluginContainer* _container { nullptr }; + DisplayPluginList _displayPlugins; + InputPluginList _inputPlugins; }; diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index fdae04f267..e475a2e445 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -9,21 +9,25 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "FileScriptingInterface.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(Q_OS_ANDROID) #include #include +#endif -#include "FileScriptingInterface.h" #include "ResourceManager.h" #include "ScriptEngineLogging.h" @@ -68,7 +72,9 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool } QStringList FileScriptingInterface::unzipFile(QString path, QString tempDir) { - +#if defined(Q_OS_ANDROID) + return QStringList(); +#else QDir dir(path); QString dirName = dir.path(); qCDebug(scriptengine) << "Directory to unzip: " << dirName; @@ -83,7 +89,7 @@ QStringList FileScriptingInterface::unzipFile(QString path, QString tempDir) { qCDebug(scriptengine) << "Extraction failed"; return list; } - +#endif } // fix to check that we are only referring to a temporary directory @@ -131,11 +137,12 @@ void FileScriptingInterface::recursiveFileScan(QFileInfo file, QString* dirName) return; }*/ QFileInfoList files; - +#if !defined(Q_OS_ANDROID) if (file.fileName().contains(".zip")) { qCDebug(scriptengine) << "Extracting archive: " + file.fileName(); JlCompress::extractDir(file.fileName()); } +#endif files = file.dir().entryInfoList(); /*if (files.empty()) { diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 6fbcd6a794..792653b75b 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1607,14 +1607,14 @@ QVariantMap ScriptEngine::fetchModuleSource(const QString& modulePath, const boo if (!loader->isFinished()) { QTimer monitor; QEventLoop loop; - QObject::connect(loader, &BatchLoader::finished, this, [this, &monitor, &loop]{ + QObject::connect(loader, &BatchLoader::finished, this, [&monitor, &loop]{ monitor.stop(); loop.quit(); }); // this helps detect the case where stop() is invoked during the download // but not seen in time to abort processing in onload()... - connect(&monitor, &QTimer::timeout, this, [this, &loop, &loader]{ + connect(&monitor, &QTimer::timeout, this, [this, &loop]{ if (isStopping()) { loop.exit(-1); } @@ -2247,7 +2247,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co QTimer timeout; timeout.setSingleShot(true); timeout.start(SANDBOX_TIMEOUT); - connect(&timeout, &QTimer::timeout, [&sandbox, SANDBOX_TIMEOUT, scriptOrURL]{ + connect(&timeout, &QTimer::timeout, [&sandbox, scriptOrURL]{ qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable timeout(" << scriptOrURL << ")"; // Guard against infinite loops and non-performant code diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt index 53006b8943..2dd23f5134 100644 --- a/libraries/ui/CMakeLists.txt +++ b/libraries/ui/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME ui) -setup_hifi_library(OpenGL Multimedia Network Qml Quick Script WebChannel WebEngine WebSockets XmlPatterns) +setup_hifi_library(OpenGL Multimedia Network Qml Quick Script WebChannel WebSockets XmlPatterns ${PLATFORM_QT_COMPONENTS}) link_hifi_libraries(shared networking gl audio audio-client plugins pointers) include_hifi_library_headers(controllers) diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 9a591018f5..536b1c6127 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -276,7 +277,9 @@ private: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); +#if !defined(Q_OS_ANDROID) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f); +#endif glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); return newTexture; @@ -416,6 +419,8 @@ static size_t globalEngineRefCount{ 0 }; #endif void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) { + new QQmlFileSelector(engine, window); + // register the pixmap image provider (used only for security image, for now) engine->addImageProvider(ImageProvider::PROVIDER_NAME, new ImageProvider()); @@ -440,8 +445,10 @@ void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) { if (!javaScriptToInject.isEmpty()) { rootContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject)); } +#if !defined(Q_OS_ANDROID) rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); +#endif rootContext->setContextProperty("Paths", DependencyManager::get().data()); } @@ -615,10 +622,12 @@ void OffscreenQmlSurface::onAboutToQuit() { } void OffscreenQmlSurface::disconnectAudioOutputTimer() { +#if !defined(Q_OS_ANDROID) if (_audioOutputUpdateTimer.isActive()) { _audioOutputUpdateTimer.stop(); } QObject::disconnect(&_audioOutputUpdateTimer); +#endif } void OffscreenQmlSurface::create() { @@ -668,7 +677,8 @@ void OffscreenQmlSurface::create() { // Find a way to flag older scripts using this mechanism and wanr that this is deprecated _qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, _qmlContext)); _renderControl->initialize(_canvas->getContext()); - + +#if !defined(Q_OS_ANDROID) // Connect with the audio client and listen for audio device changes auto audioIO = DependencyManager::get(); connect(audioIO.data(), &AudioClient::deviceChanged, this, [&](QAudio::Mode mode, const QAudioDeviceInfo& device) { @@ -684,6 +694,7 @@ void OffscreenQmlSurface::create() { int waitForAudioQmlMs = 200; _audioOutputUpdateTimer.setInterval(waitForAudioQmlMs); _audioOutputUpdateTimer.setSingleShot(true); +#endif // When Quick says there is a need to render, we will not render immediately. Instead, // a timer with a small interval is used to get better performance. @@ -695,20 +706,25 @@ void OffscreenQmlSurface::create() { } void OffscreenQmlSurface::changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate) { +#if !defined(Q_OS_ANDROID) if (_rootItem != nullptr && !isHtmlUpdate) { QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); } emit audioOutputDeviceChanged(deviceName); +#endif } void OffscreenQmlSurface::forceHtmlAudioOutputDeviceUpdate() { +#if !defined(Q_OS_ANDROID) auto audioIO = DependencyManager::get(); QString deviceName = audioIO->getActiveAudioDevice(QAudio::AudioOutput).deviceName(); QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection, Q_ARG(QString, deviceName), Q_ARG(bool, true)); +#endif } void OffscreenQmlSurface::forceQmlAudioOutputDeviceUpdate() { +#if !defined(Q_OS_ANDROID) if (QThread::currentThread() != qApp->thread()) { QMetaObject::invokeMethod(this, "forceQmlAudioOutputDeviceUpdate", Qt::QueuedConnection); } else { @@ -719,6 +735,7 @@ void OffscreenQmlSurface::forceQmlAudioOutputDeviceUpdate() { } _audioOutputUpdateTimer.start(); } +#endif } static uvec2 clampSize(const uvec2& size, uint32_t maxDimension) { @@ -1348,14 +1365,11 @@ void OffscreenQmlSurface::lowerKeyboard() { void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool numeric, bool passwordField) { qCDebug(uiLogging) << "setKeyboardRaised: " << object << ", raised: " << raised << ", numeric: " << numeric << ", password: " << passwordField; -#if Q_OS_ANDROID - return; -#endif - if (!object) { return; } +#if !defined(Q_OS_ANDROID) // if HMD is being worn, allow keyboard to open. allow it to close, HMD or not. if (!raised || qApp->property(hifi::properties::HMD).toBool()) { QQuickItem* item = dynamic_cast(object); @@ -1392,6 +1406,7 @@ void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool n item = dynamic_cast(item->parentItem()); } } +#endif } void OffscreenQmlSurface::emitScriptEvent(const QVariant& message) { diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 4c23c62c12..03f7475f61 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -173,9 +173,11 @@ private: uint64_t _lastRenderTime { 0 }; uvec2 _size; +#if !defined(Q_OS_ANDROID) QTimer _audioOutputUpdateTimer; QString _currentAudioOutputDevice; - +#endif + // Texture management TextureAndFence _latestTextureAndFence { 0, 0 }; diff --git a/libraries/ui/src/ui/types/FileTypeProfile.cpp b/libraries/ui/src/ui/types/FileTypeProfile.cpp index 6fcd8df669..90a2c6ba18 100644 --- a/libraries/ui/src/ui/types/FileTypeProfile.cpp +++ b/libraries/ui/src/ui/types/FileTypeProfile.cpp @@ -13,6 +13,7 @@ #include "FileTypeRequestInterceptor.h" +#if !defined(Q_OS_ANDROID) static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; FileTypeProfile::FileTypeProfile(QObject* parent) : @@ -24,3 +25,4 @@ FileTypeProfile::FileTypeProfile(QObject* parent) : auto requestInterceptor = new FileTypeRequestInterceptor(this); setRequestInterceptor(requestInterceptor); } +#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/FileTypeProfile.h b/libraries/ui/src/ui/types/FileTypeProfile.h index f922fd66de..c7d07cd822 100644 --- a/libraries/ui/src/ui/types/FileTypeProfile.h +++ b/libraries/ui/src/ui/types/FileTypeProfile.h @@ -14,12 +14,15 @@ #ifndef hifi_FileTypeProfile_h #define hifi_FileTypeProfile_h +#include + +#if !defined(Q_OS_ANDROID) #include class FileTypeProfile : public QQuickWebEngineProfile { public: FileTypeProfile(QObject* parent = Q_NULLPTR); }; - +#endif #endif // hifi_FileTypeProfile_h diff --git a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.cpp b/libraries/ui/src/ui/types/FileTypeRequestInterceptor.cpp index 91bacd46a6..25866ad395 100644 --- a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.cpp +++ b/libraries/ui/src/ui/types/FileTypeRequestInterceptor.cpp @@ -15,7 +15,11 @@ #include "RequestFilters.h" +#if !defined(Q_OS_ANDROID) + void FileTypeRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { RequestFilters::interceptHFWebEngineRequest(info); RequestFilters::interceptFileType(info); } + +#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.h b/libraries/ui/src/ui/types/FileTypeRequestInterceptor.h index be971daf7a..b8a01a53fa 100644 --- a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.h +++ b/libraries/ui/src/ui/types/FileTypeRequestInterceptor.h @@ -14,6 +14,9 @@ #ifndef hifi_FileTypeRequestInterceptor_h #define hifi_FileTypeRequestInterceptor_h +#include + +#if !defined(Q_OS_ANDROID) #include class FileTypeRequestInterceptor : public QWebEngineUrlRequestInterceptor { @@ -22,5 +25,6 @@ public: virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override; }; +#endif #endif // hifi_FileTypeRequestInterceptor_h diff --git a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp index 685af45dad..381bdb10bd 100644 --- a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp +++ b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp @@ -13,6 +13,8 @@ #include "HFWebEngineRequestInterceptor.h" +#if !defined(Q_OS_ANDROID) + static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; HFWebEngineProfile::HFWebEngineProfile(QObject* parent) : @@ -24,3 +26,5 @@ HFWebEngineProfile::HFWebEngineProfile(QObject* parent) : auto requestInterceptor = new HFWebEngineRequestInterceptor(this); setRequestInterceptor(requestInterceptor); } + +#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/HFWebEngineProfile.h b/libraries/ui/src/ui/types/HFWebEngineProfile.h index 5c7655479e..30da489c92 100644 --- a/libraries/ui/src/ui/types/HFWebEngineProfile.h +++ b/libraries/ui/src/ui/types/HFWebEngineProfile.h @@ -14,12 +14,15 @@ #ifndef hifi_HFWebEngineProfile_h #define hifi_HFWebEngineProfile_h +#include + +#if !defined(Q_OS_ANDROID) #include class HFWebEngineProfile : public QQuickWebEngineProfile { public: HFWebEngineProfile(QObject* parent = Q_NULLPTR); }; - +#endif #endif // hifi_HFWebEngineProfile_h diff --git a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.cpp b/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.cpp index eaf0de7245..5a11c32efa 100644 --- a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.cpp +++ b/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.cpp @@ -16,6 +16,10 @@ #include "AccountManager.h" #include "RequestFilters.h" +#if !defined(Q_OS_ANDROID) + void HFWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { RequestFilters::interceptHFWebEngineRequest(info); } + +#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.h b/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.h index a4c308426c..b5521a106e 100644 --- a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.h +++ b/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.h @@ -14,6 +14,9 @@ #ifndef hifi_HFWebEngineRequestInterceptor_h #define hifi_HFWebEngineRequestInterceptor_h +#include + +#if !defined(Q_OS_ANDROID) #include class HFWebEngineRequestInterceptor : public QWebEngineUrlRequestInterceptor { @@ -22,5 +25,6 @@ public: virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override; }; +#endif #endif // hifi_HFWebEngineRequestInterceptor_h diff --git a/libraries/ui/src/ui/types/RequestFilters.cpp b/libraries/ui/src/ui/types/RequestFilters.cpp index 0a0e67756d..12e143a726 100644 --- a/libraries/ui/src/ui/types/RequestFilters.cpp +++ b/libraries/ui/src/ui/types/RequestFilters.cpp @@ -17,6 +17,8 @@ #include "AccountManager.h" +#if !defined(Q_OS_ANDROID) + namespace { bool isAuthableHighFidelityURL(const QUrl& url) { @@ -77,3 +79,4 @@ void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info) { info.setHttpHeader(CONTENT_HEADER.toLocal8Bit(), TYPE_VALUE.toLocal8Bit()); } } +#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/RequestFilters.h b/libraries/ui/src/ui/types/RequestFilters.h index 0d7d66e155..ccab6a6ee3 100644 --- a/libraries/ui/src/ui/types/RequestFilters.h +++ b/libraries/ui/src/ui/types/RequestFilters.h @@ -14,15 +14,17 @@ #ifndef hifi_RequestFilters_h #define hifi_RequestFilters_h +#include + +#if !defined(Q_OS_ANDROID) #include #include class RequestFilters : public QObject { - Q_OBJECT - -public: +public: static void interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info); static void interceptFileType(QWebEngineUrlRequestInfo& info); }; +#endif #endif // hifi_RequestFilters_h From a776c415ebdba7a4705ef7b534bd96d48129bd6b Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 11 Dec 2017 13:35:56 -0800 Subject: [PATCH 3/7] Fix gradle dependencies --- android/build.gradle | 115 ++++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 8e7f728f98..015f80967b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -163,7 +163,6 @@ def scanQmlImports = { File qmlRootPath -> " -importPath ${qmlRoot.absolutePath}/qml" def commandResult = captureOutput(command) - println commandResult new JsonSlurper().parseText(commandResult).each { if (!it.containsKey('path')) { println "Warning: QML import could not be resolved in any of the import paths: ${it.name}" @@ -245,6 +244,58 @@ def parseQtDependencies = { List qtLibs -> }) } +def generateLibsXml = { + def libDestinationDirectory = jniFolder + def jarDestinationDirectory = new File(appDir, 'libs') + def assetDestinationDirectory = new File(appDir, 'src/main/assets/bundled'); + def libsXmlFile = new File(appDir, 'src/main/res/values/libs.xml') + def libPrefix = 'lib' + File.separator + def jarPrefix = 'jar' + File.separator + + def xmlParser = new XmlParser() + def libsXmlRoot = xmlParser.parseText('') + def qtLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'qt_libs']) + def bundledLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_lib']) + def bundledAssetsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_assets']) + + options.files.each { + def sourceFile = it + if (!sourceFile.exists()) { + throw new GradleException("Unable to find dependency file " + sourceFile.toString()) + } + + def relativePath = relativize( qmlRoot, sourceFile ).toString() + def destinationFile + if (relativePath.endsWith('.so')) { + def garbledFileName + if (relativePath.startsWith(libPrefix)) { + garbledFileName = relativePath.substring(libPrefix.size()) + Pattern p = ~/lib(Qt5.*).so/ + Matcher m = p.matcher(garbledFileName) + assert m.matches() + def libName = m.group(1) + xmlParser.createNode(qtLibsNode, 'item', [:]).setValue(libName) + } else { + garbledFileName = 'lib' + relativePath.replace(File.separator, '_'[0]) + xmlParser.createNode(bundledLibsNode, 'item', [:]).setValue("${garbledFileName}:${relativePath}".replace(File.separator, '/')) + } + destinationFile = new File(libDestinationDirectory, garbledFileName) + } else if (relativePath.startsWith('jar')) { + destinationFile = new File(jarDestinationDirectory, relativePath.substring(jarPrefix.size())) + } else { + xmlParser.createNode(bundledAssetsNode, 'item', [:]).setValue("bundled/${relativePath}:${relativePath}".replace(File.separator, '/')) + destinationFile = new File(assetDestinationDirectory, relativePath) + } + + copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } + assert destinationFile.exists() && destinationFile.isFile() + } + def xml = XmlUtil.serialize(libsXmlRoot) + new FileWriter(libsXmlFile).withPrintWriter { writer -> + writer.write(xml) + } +} + task downloadDependencies { doLast { packages.each { entry -> @@ -298,7 +349,7 @@ task extractDependencies(dependsOn: verifyDependencyDownloads) { } // Copies the non Qt dependencies. Qt dependencies (primary libraries and plugins) are handled by the qtBundle task -task copyDependenciesImpl { +task copyDependencies(dependsOn: [ extractDependencies ]) { doLast { packages.each { entry -> def packageName = entry.key @@ -320,8 +371,6 @@ task copyDependenciesImpl { } } -task copyDependencies(dependsOn: [ extractDependencies, copyDependenciesImpl ]) { } - task downloadScribe(type: Download) { src baseUrl + scribeFile dest new File(baseFolder, scribeLocalFile) @@ -376,59 +425,11 @@ task extractGvrBinaries(dependsOn: extractDependencies) { task qtBundle { doLast { parseQtDependencies(QT5_DEPS) - //scanQmlImports(new File("${appDir}/../../interface/resources/qml/")) - scanQmlImports(new File("${projectDir}/app/src/main/cpp")) - - def libDestinationDirectory = jniFolder - def jarDestinationDirectory = new File(appDir, 'libs') - def assetDestinationDirectory = new File(appDir, 'src/main/assets/bundled'); - def libsXmlFile = new File(appDir, 'src/main/res/values/libs.xml') - def libPrefix = 'lib' + File.separator - def jarPrefix = 'jar' + File.separator - - def xmlParser = new XmlParser() - def libsXmlRoot = xmlParser.parseText('') - def qtLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'qt_libs']) - def bundledLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_lib']) - def bundledAssetsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_assets']) - - options.files.each { - def sourceFile = it - if (!sourceFile.exists()) { - throw new GradleException("Unable to find dependency file " + sourceFile.toString()) - } - - def relativePath = relativize( qmlRoot, sourceFile ).toString() - def destinationFile - if (relativePath.endsWith('.so')) { - def garbledFileName - if (relativePath.startsWith(libPrefix)) { - garbledFileName = relativePath.substring(libPrefix.size()) - Pattern p = ~/lib(Qt5.*).so/ - Matcher m = p.matcher(garbledFileName) - assert m.matches() - def libName = m.group(1) - xmlParser.createNode(qtLibsNode, 'item', [:]).setValue(libName) - } else { - garbledFileName = 'lib' + relativePath.replace(File.separator, '_'[0]) - xmlParser.createNode(bundledLibsNode, 'item', [:]).setValue("${garbledFileName}:${relativePath}".replace(File.separator, '/')) - } - destinationFile = new File(libDestinationDirectory, garbledFileName) - } else if (relativePath.startsWith('jar')) { - destinationFile = new File(jarDestinationDirectory, relativePath.substring(jarPrefix.size())) - } else { - xmlParser.createNode(bundledAssetsNode, 'item', [:]).setValue("bundled/${relativePath}:${relativePath}".replace(File.separator, '/')) - destinationFile = new File(assetDestinationDirectory, relativePath) - } - - copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } - assert destinationFile.exists() && destinationFile.isFile() - } - def xml = XmlUtil.serialize(libsXmlRoot) - new FileWriter(libsXmlFile).withPrintWriter { writer -> - writer.write(xml) - } - } + //def qmlImportFolder = new File("${appDir}/../../interface/resources/qml/") + def qmlImportFolder = new File("${projectDir}/app/src/main/cpp") + scanQmlImports(qmlImportFolder) + generateLibsXml() + } } task setupDependencies(dependsOn: [setupScribe, copyDependencies, extractGvrBinaries, qtBundle]) { } From 282ae7b6f91452219cdc33ddc9ec800d8d318b2c Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 11 Dec 2017 14:42:37 -0800 Subject: [PATCH 4/7] Small fixes --- android/app/CMakeLists.txt | 4 ++-- libraries/gl/src/gl/OffscreenGLCanvas.cpp | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt index 6df17c3dc7..450aa7bc5e 100644 --- a/android/app/CMakeLists.txt +++ b/android/app/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME native-lib) setup_hifi_library(Gui Qml Quick) -#link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics entities octree audio midi recording controllers script-engine ui) -link_hifi_libraries(shared networking gl ui) +link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics entities octree audio midi recording controllers script-engine ui) +#link_hifi_libraries(shared networking gl ui) target_link_libraries(native-lib android log m) target_opengl() diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index eb99933101..d5d104098f 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -52,7 +52,7 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { _offscreenSurface->setFormat(_context->format()); _offscreenSurface->create(); -#if !defined(Q_OS_ANDROID) +#if defined(Q_OS_ANDROID) if (!_context->makeCurrent(_offscreenSurface)) { qFatal("Unable to make offscreen surface current"); } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 792653b75b..c7034adf35 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -2247,7 +2247,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co QTimer timeout; timeout.setSingleShot(true); timeout.start(SANDBOX_TIMEOUT); - connect(&timeout, &QTimer::timeout, [&sandbox, scriptOrURL]{ + connect(&timeout, &QTimer::timeout, [=, &sandbox]{ qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable timeout(" << scriptOrURL << ")"; // Guard against infinite loops and non-performant code From 6cff86b0f35752a6fe4bf2008353453a9e979617 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 12 Dec 2017 17:56:17 -0800 Subject: [PATCH 5/7] Fix polyvox archive, correct build errors, add additional libraries --- BUILD_ANDROID.md | 4 ++-- CMakeLists.txt | 2 ++ android/app/CMakeLists.txt | 22 +++++++++++++++++-- android/app/src/main/cpp/main.cpp | 6 +---- android/build.gradle | 2 +- interface/CMakeLists.txt | 4 ++-- libraries/avatars-renderer/CMakeLists.txt | 3 +-- libraries/display-plugins/CMakeLists.txt | 2 +- libraries/entities-renderer/CMakeLists.txt | 7 +----- .../src/EntityTreeRenderer.h | 6 ++--- .../src/RenderableEntityItem.cpp | 2 +- .../src/ConsoleScriptingInterface.h | 5 +++-- 12 files changed, 38 insertions(+), 27 deletions(-) diff --git a/BUILD_ANDROID.md b/BUILD_ANDROID.md index 5d2e6b9293..3d6d056d79 100644 --- a/BUILD_ANDROID.md +++ b/BUILD_ANDROID.md @@ -2,9 +2,9 @@ Please read the [general build guide](BUILD.md) for information on building othe # Dependencies -*Currently Android building is only supported on 64 bit Linux host environments* +Building is currently supported on OSX, Windows and Linux platforms, but developers intending to do work on the library dependencies are strongly urged to use 64 bit Linux as a build platform -You will need the following tools to build our Android targets. +You will need the following tools to build Android targets. * [Gradle](https://gradle.org/install/) * [Android Studio](https://developer.android.com/studio/index.html) diff --git a/CMakeLists.txt b/CMakeLists.txt index e033efa982..34ff672067 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,10 @@ endif() if (ANDROID) set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) + set(PLATFORM_GL_BACKEND gpu-gles) else () set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) + set(PLATFORM_GL_BACKEND gpu-gl) endif () foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS}) diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt index 450aa7bc5e..4411b7b1bb 100644 --- a/android/app/CMakeLists.txt +++ b/android/app/CMakeLists.txt @@ -1,8 +1,26 @@ set(TARGET_NAME native-lib) setup_hifi_library(Gui Qml Quick) -link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics entities octree audio midi recording controllers script-engine ui) + +# Minimal dependencies for testing UI compositing #link_hifi_libraries(shared networking gl ui) +link_hifi_libraries( + shared networking octree + script-engine recording trackers + gl ktx image gpu gpu-gles render render-utils + physics + audio audio-client + ui midi controllers pointers + model model-networking fbx animation + entities entities-renderer + avatars avatars-renderer + ui-plugins input-plugins + # display-plugins + # auto-updater +) + + target_link_libraries(native-lib android log m) target_opengl() -target_bullet() \ No newline at end of file +target_bullet() + diff --git a/android/app/src/main/cpp/main.cpp b/android/app/src/main/cpp/main.cpp index 24f264d327..b947323720 100644 --- a/android/app/src/main/cpp/main.cpp +++ b/android/app/src/main/cpp/main.cpp @@ -89,9 +89,7 @@ int main(int argc, char* argv[]) qFatal("Unable to create primary offscreen context"); } qt_gl_set_global_share_context(sharedCanvas.getContext()); - auto globalContext = QOpenGLContext::globalShareContext(); - bool threadedGlRendering = QOpenGLContext::supportsThreadedOpenGL(); GLWindow window; window.create(); @@ -104,9 +102,7 @@ int main(int argc, char* argv[]) GLuint fbo = 0; glGenFramebuffers(1, &fbo); - - - ivec2 offscreenSize { 640, 480 }; + static const ivec2 offscreenSize { 640, 480 }; OffscreenQmlSurface::setSharedContext(sharedCanvas.getContext()); OffscreenQmlSurface* qmlSurface = new OffscreenQmlSurface(); diff --git a/android/build.gradle b/android/build.gradle index 015f80967b..653c3f84b5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -101,7 +101,7 @@ def packages = [ ], polyvox: [ file: 'polyvox_armv8-libcpp.tgz', - checksum: '5c918288741ee754c16aeb12bb46b9e1', + checksum: '349ad5b72aaf2749ca95d847e60c5314', sharedLibFolder: 'lib', includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'], ], diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index a38d428eba..7625d69211 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -201,14 +201,14 @@ endif() # link required hifi libraries link_hifi_libraries( - shared octree ktx gpu gl gpu-gl procedural model render + shared octree ktx gpu gl procedural model render pointers recording fbx networking model-networking entities avatars trackers audio audio-client animation script-engine physics render-utils entities-renderer avatars-renderer ui auto-updater midi controllers plugins image trackers ui-plugins display-plugins input-plugins - ${NON_ANDROID_LIBRARIES} + ${PLATFORM_GL_BACKEND} ) # include the binary directory of render-utils for shader includes diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index bfbeab086d..148835965e 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -1,12 +1,11 @@ set(TARGET_NAME avatars-renderer) AUTOSCRIBE_SHADER_LIB(gpu model render render-utils) setup_hifi_library(Widgets Network Script) -link_hifi_libraries(shared gpu model animation model-networking script-engine render image render-utils) +link_hifi_libraries(shared gpu model animation model-networking script-engine render render-utils image trackers entities-renderer) include_hifi_library_headers(avatars) include_hifi_library_headers(networking) include_hifi_library_headers(fbx) include_hifi_library_headers(recording) -include_hifi_library_headers(trackers) include_hifi_library_headers(ktx) include_hifi_library_headers(procedural) include_hifi_library_headers(physics) diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt index 83a1c635ca..1fd855e6aa 100644 --- a/libraries/display-plugins/CMakeLists.txt +++ b/libraries/display-plugins/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME display-plugins) AUTOSCRIBE_SHADER_LIB(gpu display-plugins) setup_hifi_library(OpenGL) -link_hifi_libraries(shared plugins ui-plugins gl gpu-gl ui render-utils) +link_hifi_libraries(shared plugins ui-plugins gl ui render-utils ${PLATFORM_GL_BACKEND}) include_hifi_library_headers(gpu) include_hifi_library_headers(model-networking) include_hifi_library_headers(networking) diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 7ac6456081..cf94ecc37c 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -15,9 +15,4 @@ include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) target_bullet() - -add_dependency_external_projects(polyvox) -find_package(PolyVox REQUIRED) -target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES}) - +target_polyvox() \ No newline at end of file diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 04048e5f92..dccd14aac6 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -12,13 +12,13 @@ #ifndef hifi_EntityTreeRenderer_h #define hifi_EntityTreeRenderer_h -#include -#include +#include +#include +#include #include #include // for RayToEntityIntersectionResult #include -#include #include #include #include diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 24de651247..9e32977de1 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -373,7 +373,7 @@ void EntityRenderer::onAddToScene(const EntityItemPointer& entity) { renderer->onEntityChanged(_entity->getID()); } }, Qt::QueuedConnection); - _changeHandlerId = entity->registerChangeHandler([this](const EntityItemID& changedEntity) { + _changeHandlerId = entity->registerChangeHandler([](const EntityItemID& changedEntity) { auto renderer = DependencyManager::get(); if (renderer) { renderer->onEntityChanged(changedEntity); diff --git a/libraries/script-engine/src/ConsoleScriptingInterface.h b/libraries/script-engine/src/ConsoleScriptingInterface.h index 444ea93504..deb0d126f9 100644 --- a/libraries/script-engine/src/ConsoleScriptingInterface.h +++ b/libraries/script-engine/src/ConsoleScriptingInterface.h @@ -21,9 +21,10 @@ #include #include +#include +#include +#include #include -#include -#include // Scriptable interface of "console" object. Used exclusively in the JavaScript API class ConsoleScriptingInterface : public QObject, protected QScriptable { From 6cf99e0b0380085adf4db546fa44deb52d1060b9 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sat, 16 Dec 2017 16:17:31 -0800 Subject: [PATCH 6/7] Add version IDs to dependency downloads --- android/build.gradle | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 653c3f84b5..903c3831bc 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -66,47 +66,58 @@ def baseUrl = 'https://hifi-public.s3.amazonaws.com/austin/android/' def qtFile='qt-5.9.3_linux_armv8-libcpp_openssl.tgz' def qtChecksum='04599670ccca84bd2b15f6915568eb2d' +def qtVersionId='PeoqzN31n.YvLfs9JE2SgHgZ4.IaKAlt' if (Os.isFamily(Os.FAMILY_MAC)) { qtFile = 'qt-5.9.3_osx_armv8-libcpp_openssl.tgz' qtChecksum='4b02de9d67d6bfb202355a808d2d9c59' + qtVersionId='HygCmtMLPYioyil0DfXckGVzhw2SXZA9' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz' qtChecksum='a93d22c0c59aa112fda18c4c6d157d17' + qtVersionId='0Bl9NSUWb5CBKLT_NXaxTt75SNBBZ9sB' } def packages = [ qt: [ file: qtFile, + versionId: qtVersionId, checksum: qtChecksum, ], bullet: [ file: 'bullet-2.83_armv8-libcpp.tgz', - checksum: '2c558d604fce337f5eba3eb7ec1252fd' + versionId: 'ljb7v.1IjVRqyopUKVDbVnLA4z88J8Eo', + checksum: '2c558d604fce337f5eba3eb7ec1252fd', ], draco: [ file: 'draco_armv8-libcpp.tgz', - checksum: '617a80d213a5ec69fbfa21a1f2f738cd' + versionId: 'cA3tVJSmkvb1naA3l6D_Jv2Noh.4yc4m', + checksum: '617a80d213a5ec69fbfa21a1f2f738cd', ], glm: [ file: 'glm-0.9.8.tgz', - checksum: 'd2b42cee31d2bc17bab6ce69e6b3f30a' + versionId: 'BlkJNwaYV2Gfy5XwMeU7K0uzPDRKFMt2', + checksum: 'd2b42cee31d2bc17bab6ce69e6b3f30a', ], gvr: [ file: 'gvrsdk_v1.101.0.tgz', - checksum: '57fd02baa069176ba18597a29b6b4fc7' + versionId: 'UTberAIFraEfF9IVjoV66u1DTPTopgeY', + checksum: '57fd02baa069176ba18597a29b6b4fc7', ], openssl: [ file: 'openssl-1.1.0g_armv8.tgz', - checksum: 'cabb681fbccd79594f65fcc266e02f32' + versionId: 'DmahmSGFS4ltpHyTdyQvv35WOeUOiib9', + checksum: 'cabb681fbccd79594f65fcc266e02f32', ], polyvox: [ file: 'polyvox_armv8-libcpp.tgz', + versionId: 'LDJtzMTvdm4SAc2KYg8Cg6uwWk4Vq3e3', checksum: '349ad5b72aaf2749ca95d847e60c5314', sharedLibFolder: 'lib', includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'], ], tbb: [ file: 'tbb-2018_U1_armv8_libcpp.tgz', + versionId: 'YZliDD8.Menh1IVXKEuLPeO3xAjJ1UdF', checksum: '20768f298f53b195e71b414b0ae240c4', sharedLibFolder: 'lib/release', includeLibs: ['libtbb.so', 'libtbbmalloc.so'], @@ -124,7 +135,6 @@ if (Os.isFamily(Os.FAMILY_MAC)) { } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { scribeFile = 'scribe_win32_x86_64.exe' scribeChecksum='75c2ce9ed45d17de375e3988bfaba816' - } def options = [ @@ -301,6 +311,9 @@ task downloadDependencies { packages.each { entry -> def filename = entry.value['file']; def url = baseUrl + filename; + if (entry.value.containsKey('versionId')) { + url = url + '?versionId=' + entry.value['versionId'] + } download { src url dest new File(baseFolder, filename) From 26291e0a75fc6a74e1374339baa5b55e68135506 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 21 Dec 2017 07:29:29 -0800 Subject: [PATCH 7/7] PR feedback --- android/build.gradle | 2 ++ libraries/entities-renderer/CMakeLists.txt | 2 +- libraries/gl/src/gl/Config.h | 13 ++++++++++--- libraries/gl/src/gl/GLHelpers.cpp | 2 +- libraries/gl/src/gl/OffscreenGLCanvas.cpp | 9 ++++----- .../script-engine/src/FileScriptingInterface.cpp | 3 +++ 6 files changed, 21 insertions(+), 10 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 903c3831bc..c7eefd051b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -454,6 +454,8 @@ task cleanDependencies(type: Delete) { delete 'app/src/main/res/values/libs.xml' } +// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution. +// See the comment on the qtBundle task above /* // FIXME derive the path from the gradle environment def toolchain = [ diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index cf94ecc37c..19987197b8 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -15,4 +15,4 @@ include_hifi_library_headers(avatars) include_hifi_library_headers(controllers) target_bullet() -target_polyvox() \ No newline at end of file +target_polyvox() diff --git a/libraries/gl/src/gl/Config.h b/libraries/gl/src/gl/Config.h index b1bafe1ba6..cdf86675c6 100644 --- a/libraries/gl/src/gl/Config.h +++ b/libraries/gl/src/gl/Config.h @@ -15,6 +15,11 @@ #include #if defined(Q_OS_ANDROID) +#define HIFI_GLES +#define HIFI_EGL +#endif + +#if defined(HIFI_GLES) // Minimum GL ES version required is 3.2 #define GL_MIN_VERSION_MAJOR 0x03 #define GL_MIN_VERSION_MINOR 0x02 @@ -30,9 +35,11 @@ #define MINIMUM_GL_VERSION ((GL_MIN_VERSION_MAJOR << 8) | GL_MIN_VERSION_MINOR) -#if defined(Q_OS_ANDROID) - +#if defined(HIFI_GLES) #include +#endif + +#if defined(HIFI_GLES) #include #define GL_DEPTH_COMPONENT32_OES 0x81A7 @@ -55,7 +62,7 @@ extern "C" { extern PFNGLFRAMEBUFFERTEXTUREEXTPROC glFramebufferTextureEXT; } -#else // !defined(Q_OS_ANDROID) +#else // !defined(HIFI_GLES) #define GL_GLEXT_PROTOTYPES 1 #include diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 0e3321d99d..722253c26f 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -28,7 +28,7 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { static QSurfaceFormat format; static std::once_flag once; std::call_once(once, [] { -#if defined(Q_OS_ANDROID) +#if defined(HIFI_GLES) format.setRenderableType(QSurfaceFormat::OpenGLES); format.setRedBufferSize(8); format.setGreenBufferSize(8); diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index d5d104098f..67eb3520c9 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -52,16 +52,15 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { _offscreenSurface->setFormat(_context->format()); _offscreenSurface->create(); + + // Due to a https://bugreports.qt.io/browse/QTBUG-65125 we can't rely on `isValid` + // to determine if the offscreen surface was successfully created, so we use + // makeCurrent as a proxy test. Bug is fixed in Qt 5.9.4 #if defined(Q_OS_ANDROID) if (!_context->makeCurrent(_offscreenSurface)) { qFatal("Unable to make offscreen surface current"); } #else - // For some reason, the offscreen surface is considered invalid on android - // possibly because of a bad format? Would need to add some logging to the - // eglpbuffer implementation used from the android platform plugin. - // Alternatively investigate the use of an invisible surface view to do - // a 'native' offscreen surface if (!_offscreenSurface->isValid()) { qFatal("Offscreen surface is invalid"); } diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index e475a2e445..1472e53045 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -23,6 +23,7 @@ #include #include +// FIXME quazip hasn't been built on the android toolchain #if !defined(Q_OS_ANDROID) #include #include @@ -73,6 +74,7 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool QStringList FileScriptingInterface::unzipFile(QString path, QString tempDir) { #if defined(Q_OS_ANDROID) + // FIXME quazip hasn't been built on the android toolchain return QStringList(); #else QDir dir(path); @@ -137,6 +139,7 @@ void FileScriptingInterface::recursiveFileScan(QFileInfo file, QString* dirName) return; }*/ QFileInfoList files; + // FIXME quazip hasn't been built on the android toolchain #if !defined(Q_OS_ANDROID) if (file.fileName().contains(".zip")) { qCDebug(scriptengine) << "Extracting archive: " + file.fileName();