From 0379ebad63ad6afa0fe7adfa5bf21c347acb8a65 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 3 Dec 2017 10:50:34 -0800 Subject: [PATCH] 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