diff --git a/.gitignore b/.gitignore index 5a965b494c..4c262f9f4a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ android/**/bin android/**/src/main/res/values/libs.xml android/**/src/main/assets android/**/gradle* +*.class # VSCode # List taken from Github Global Ignores master@435c4d92 diff --git a/BUILD_QUEST.md b/BUILD_QUEST.md new file mode 100644 index 0000000000..e093969f83 --- /dev/null +++ b/BUILD_QUEST.md @@ -0,0 +1,65 @@ +Please read the [general build guide](BUILD.md) for information on building other platform. Only Quest specific instructions are found in this file. + +# Dependencies + +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 Android targets. + +* [Android Studio](https://developer.android.com/studio/index.html) + +### Android Studio + +Download the Android Studio installer and run it. Once installed, at the welcome screen, click configure in the lower right corner and select SDK manager + +From the SDK Platforms tab, select API levels 24 and 26. + +From the SDK Tools tab select the following + +* Android SDK Build-Tools +* GPU Debugging Tools +* CMake (even if you have a separate CMake installation) +* LLDB +* Android SDK Platform-Tools +* Android SDK Tools +* NDK (even if you have the NDK installed separately) + +Make sure the NDK installed version is 18 (or higher) + +# Environment + +Setting up the environment for android builds requires some additional steps + +#### Set up machine specific Gradle properties + +Create a `gradle.properties` file in $HOME/.gradle. Edit the file to contain the following + + HIFI_ANDROID_PRECOMPILED=/Android/hifi_externals + HIFI_ANDROID_KEYSTORE=/.jks + HIFI_ANDROID_KEYSTORE_PASSWORD= + HIFI_ANDROID_KEY_ALIAS= + HIFI_ANDROID_KEY_PASSWORD= + +Note, do not use `$HOME` for the path. It must be a fully qualified path name. + +### Setup the repository + +Clone the repository + +`git clone https://github.com/highfidelity/hifi.git` + +Enter the repository `android` directory + +`cd hifi/android` + +# Building & Running + +* Open Android Studio +* Choose _Open Existing Android Studio Project_ +* Navigate to the `hifi` repository and choose the `android` folder and select _OK_ +* Open Gradle.settings and comment out any projects not necessary +* From _File_ menu select _Sync with File System_ to resync Gradle settings +* From the _Build_ menu select _Make Project_ +* From +* Once the build completes, from the _Run_ menu select _Run App_ + diff --git a/CMakeLists.txt b/CMakeLists.txt index c8710eed05..c126dce56a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,13 @@ if (ANDROID) add_definitions(-DCUSTOM_DISPLAY_PLUGINS) set(PLATFORM_PLUGIN_LIBRARIES oculusMobile oculusMobilePlugin) endif() + + # Allow client code to use preprocessor macros to distinguish between quest and non-quest builds + if (${HIFI_ANDROID_APP} STREQUAL "questInterface") + add_definitions(-DANDROID_APP_QUEST_INTERFACE) + elseif(${HIFI_ANDROID_APP} STREQUAL "interface") + add_definitions(-DANDROID_APP_INTERFACE) + endif() else () set(PLATFORM_QT_COMPONENTS WebEngine Xml) endif () diff --git a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index a7bda3c29b..3cdb9f5a09 100644 --- a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -81,6 +81,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW private boolean nativeEnterBackgroundCallEnqueued = false; private SlidingDrawer mWebSlidingDrawer; private boolean mStartInDomain; + private boolean isLoading; // private GvrApi gvrApi; // Opaque native pointer to the Application C++ object. // This object is owned by the InterfaceActivity instance and passed to the native methods. @@ -94,7 +95,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW @Override public void onCreate(Bundle savedInstanceState) { - super.isLoading = true; + isLoading = true; Intent intent = getIntent(); if (intent.hasExtra(DOMAIN_URL) && !TextUtils.isEmpty(intent.getStringExtra(DOMAIN_URL))) { intent.putExtra("applicationArguments", "--url " + intent.getStringExtra(DOMAIN_URL)); @@ -145,7 +146,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW @Override protected void onPause() { super.onPause(); - if (super.isLoading) { + if (isLoading) { nativeEnterBackgroundCallEnqueued = true; } else { nativeEnterBackground(); @@ -172,7 +173,6 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW super.onResume(); nativeEnterForeground(); surfacesWorkaround(); - keepInterfaceRunning = false; registerReceiver(headsetStateReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); //gvrApi.resumeTracking(); } @@ -382,7 +382,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW } public void onAppLoadedComplete() { - super.isLoading = false; + isLoading = false; if (nativeEnterBackgroundCallEnqueued) { nativeEnterBackground(); } @@ -413,7 +413,6 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW @Override public void onExpand() { - keepInterfaceRunning = true; } @Override diff --git a/android/apps/questFramePlayer/CMakeLists.txt b/android/apps/questFramePlayer/CMakeLists.txt index 5889585a6c..ea60e27a7d 100644 --- a/android/apps/questFramePlayer/CMakeLists.txt +++ b/android/apps/questFramePlayer/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME questFramePlayer) setup_hifi_library(AndroidExtras) link_hifi_libraries(shared ktx shaders gpu gl oculusMobile ${PLATFORM_GL_BACKEND}) -target_include_directories(${TARGET_NAME} PRIVATE ${HIFI_ANDROID_PRECOMPILED}/ovr/VrApi/Include) + target_link_libraries(${TARGET_NAME} android log m) target_opengl() target_oculus_mobile() diff --git a/android/apps/questFramePlayer/src/main/AndroidManifest.xml b/android/apps/questFramePlayer/src/main/AndroidManifest.xml index 721e8cee89..ba14b04532 100644 --- a/android/apps/questFramePlayer/src/main/AndroidManifest.xml +++ b/android/apps/questFramePlayer/src/main/AndroidManifest.xml @@ -19,24 +19,6 @@ android:name="org.qtproject.qt5.android.bindings.QtApplication" tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon"> - - - - - - - - - + + + + + + + diff --git a/android/apps/questFramePlayer/src/main/cpp/AndroidHelper.cpp b/android/apps/questFramePlayer/src/main/cpp/AndroidHelper.cpp new file mode 100644 index 0000000000..797040ab69 --- /dev/null +++ b/android/apps/questFramePlayer/src/main/cpp/AndroidHelper.cpp @@ -0,0 +1,30 @@ +// +// Created by Bradley Austin Davis on 2019/02/15 +// Copyright 2013-2019 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 +// +#include "AndroidHelper.h" + +#include +#include + +AndroidHelper::AndroidHelper() { +} + +AndroidHelper::~AndroidHelper() { +} + +void AndroidHelper::notifyLoadComplete() { + emit qtAppLoadComplete(); +} + +void AndroidHelper::notifyEnterForeground() { + emit enterForeground(); +} + +void AndroidHelper::notifyEnterBackground() { + emit enterBackground(); +} + diff --git a/android/apps/questFramePlayer/src/main/cpp/AndroidHelper.h b/android/apps/questFramePlayer/src/main/cpp/AndroidHelper.h new file mode 100644 index 0000000000..ef6722462c --- /dev/null +++ b/android/apps/questFramePlayer/src/main/cpp/AndroidHelper.h @@ -0,0 +1,43 @@ +// +// Created by Bradley Austin Davis on 2019/02/15 +// Copyright 2013-2019 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 +// + +#ifndef hifi_Android_Helper_h +#define hifi_Android_Helper_h + +#include +#include +#include +#include + +class AndroidHelper : public QObject { + Q_OBJECT +public: + AndroidHelper(AndroidHelper const&) = delete; + void operator=(AndroidHelper const&) = delete; + + static AndroidHelper& instance() { + static AndroidHelper instance; + return instance; + } + + void notifyLoadComplete(); + void notifyEnterForeground(); + void notifyEnterBackground(); + + +signals: + void qtAppLoadComplete(); + void enterForeground(); + void enterBackground(); + +private: + AndroidHelper(); + ~AndroidHelper(); +}; + +#endif diff --git a/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp index ec2986298e..8f78b1946a 100644 --- a/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp +++ b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp @@ -11,15 +11,11 @@ #include PlayerWindow::PlayerWindow() { - installEventFilter(this); - setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); + setFlags(Qt::Window); setSurfaceType(QSurface::OpenGLSurface); create(); showFullScreen(); // Ensure the window is visible and the GL context is valid QCoreApplication::processEvents(); - _renderThread.initialize(this); -} - -PlayerWindow::~PlayerWindow() { + _renderThread.initialize(); } diff --git a/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h index e4dd6cef43..5e7dc82781 100644 --- a/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h +++ b/android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h @@ -8,22 +8,13 @@ #pragma once #include -#include - -#include #include "RenderThread.h" -// Create a simple OpenGL window that renders text in various ways class PlayerWindow : public QWindow { public: PlayerWindow(); - virtual ~PlayerWindow(); - -protected: - //bool eventFilter(QObject* obj, QEvent* event) override; - //void keyPressEvent(QKeyEvent* event) override; + virtual ~PlayerWindow() {} private: - QSettings _settings; RenderThread _renderThread; }; diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp index 5eabe6b9b1..78a4487284 100644 --- a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp +++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -29,9 +30,7 @@ #include #include -static JNIEnv* _env { nullptr }; -static JavaVM* _vm { nullptr }; -static jobject _activity { nullptr }; +#include "AndroidHelper.h" struct HandController{ ovrInputTrackedRemoteCapabilities caps {}; @@ -48,21 +47,43 @@ struct HandController{ }; std::vector devices; +QAndroidJniObject __interfaceActivity; extern "C" { -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *, void *) { +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void *) { __android_log_write(ANDROID_LOG_WARN, "QQQ", __FUNCTION__); return JNI_VERSION_1_6; } +JNIEXPORT void JNICALL +Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnCreate(JNIEnv *env, jobject obj) { + __android_log_print(ANDROID_LOG_INFO, "QQQ", __FUNCTION__); + __interfaceActivity = QAndroidJniObject(obj); + QObject::connect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, []() { + __interfaceActivity.callMethod("onAppLoadedComplete", "()V"); + QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, nullptr, nullptr); + }); +} -JNIEXPORT void JNICALL Java_io_highfidelity_frameplayer_QuestQtActivity_nativeOnCreate(JNIEnv* env, jobject obj) { - env->GetJavaVM(&_vm); - _activity = env->NewGlobalRef(obj); +JNIEXPORT void +Java_io_highfidelity_oculus_OculusMobileActivity_questOnAppAfterLoad(JNIEnv *env, jobject obj) { + AndroidHelper::instance().moveToThread(qApp->thread()); } + +JNIEXPORT void JNICALL +Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnPause(JNIEnv *env, jobject obj) { + AndroidHelper::instance().notifyEnterBackground(); } +JNIEXPORT void JNICALL +Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnResume(JNIEnv *env, jobject obj) { + AndroidHelper::instance().notifyEnterForeground(); +} + +} + + static const char* FRAME_FILE = "assets:/frames/20190121_1220.json"; static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) { @@ -84,11 +105,10 @@ void RenderThread::move(const glm::vec3& v) { _correction = glm::inverse(glm::translate(mat4(), v)) * _correction; } -void RenderThread::initialize(QWindow* window) { +void RenderThread::initialize() { std::unique_lock lock(_frameLock); setObjectName("RenderThread"); Parent::initialize(); - _window = window; _thread->setObjectName("RenderThread"); } @@ -96,14 +116,7 @@ void RenderThread::setup() { // Wait until the context has been moved to this thread { std::unique_lock lock(_frameLock); } - ovr::VrHandler::initVr(); - __android_log_write(ANDROID_LOG_WARN, "QQQ", "Launching oculus activity"); - _vm->AttachCurrentThread(&_env, nullptr); - jclass cls = _env->GetObjectClass(_activity); - jmethodID mid = _env->GetMethodID(cls, "launchOculusActivity", "()V"); - _env->CallVoidMethod(_activity, mid); - __android_log_write(ANDROID_LOG_WARN, "QQQ", "Launching oculus activity done"); ovr::VrHandler::setHandler(this); makeCurrent(); @@ -169,7 +182,6 @@ void RenderThread::handleInput() { const auto &remote = controller.state; if (remote.Joystick.x != 0.0f || remote.Joystick.y != 0.0f) { glm::vec3 translation; - float rotation = 0.0f; if (caps.ControllerCapabilities & ovrControllerCaps_LeftHand) { translation = glm::vec3{0.0f, -remote.Joystick.y, 0.0f}; } else { diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.h b/android/apps/questFramePlayer/src/main/cpp/RenderThread.h index 701cd25f5b..747d0d9e8d 100644 --- a/android/apps/questFramePlayer/src/main/cpp/RenderThread.h +++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.h @@ -20,11 +20,9 @@ class RenderThread : public GenericThread, ovr::VrHandler { using Parent = GenericThread; public: - QWindow* _window{ nullptr }; std::mutex _mutex; gpu::ContextPointer _gpuContext; // initialized during window creation std::shared_ptr _backend; - std::atomic _presentCount{ 0 }; std::mutex _frameLock; std::queue _pendingFrames; gpu::FramePointer _activeFrame; @@ -39,6 +37,6 @@ public: void handleInput(); void submitFrame(const gpu::FramePointer& frame); - void initialize(QWindow* window); + void initialize(); void renderFrame(); }; diff --git a/android/apps/questFramePlayer/src/main/cpp/main.cpp b/android/apps/questFramePlayer/src/main/cpp/main.cpp index 4730d3fa15..123ba904f4 100644 --- a/android/apps/questFramePlayer/src/main/cpp/main.cpp +++ b/android/apps/questFramePlayer/src/main/cpp/main.cpp @@ -11,30 +11,33 @@ #include #include #include +#include #include #include "PlayerWindow.h" +#include "AndroidHelper.h" + void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (!message.isEmpty()) { - const char * local=message.toStdString().c_str(); + const char* local = message.toStdString().c_str(); switch (type) { case QtDebugMsg: - __android_log_write(ANDROID_LOG_DEBUG,"Interface",local); + __android_log_write(ANDROID_LOG_DEBUG, "Interface", local); break; case QtInfoMsg: - __android_log_write(ANDROID_LOG_INFO,"Interface",local); + __android_log_write(ANDROID_LOG_INFO, "Interface", local); break; case QtWarningMsg: - __android_log_write(ANDROID_LOG_WARN,"Interface",local); + __android_log_write(ANDROID_LOG_WARN, "Interface", local); break; case QtCriticalMsg: - __android_log_write(ANDROID_LOG_ERROR,"Interface",local); + __android_log_write(ANDROID_LOG_ERROR, "Interface", local); break; case QtFatalMsg: default: - __android_log_write(ANDROID_LOG_FATAL,"Interface",local); + __android_log_write(ANDROID_LOG_FATAL, "Interface", local); abort(); } } @@ -46,11 +49,13 @@ int main(int argc, char** argv) { auto oldMessageHandler = qInstallMessageHandler(messageHandler); DependencyManager::set(); PlayerWindow window; - __android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec"); + QTimer::singleShot(10, []{ + __android_log_write(ANDROID_LOG_WARN, "QQQ", "notifyLoadComplete"); + AndroidHelper::instance().notifyLoadComplete(); + }); + __android_log_write(ANDROID_LOG_WARN, "QQQ", "Exec"); app.exec(); - __android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec done"); + __android_log_write(ANDROID_LOG_WARN, "QQQ", "Exec done"); qInstallMessageHandler(oldMessageHandler); return 0; } - - diff --git a/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java deleted file mode 100644 index d498e27547..0000000000 --- a/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestQtActivity.java +++ /dev/null @@ -1,53 +0,0 @@ -// -// Created by Bradley Austin Davis on 2018/11/20 -// Copyright 2013-2018 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.frameplayer; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; - -import org.qtproject.qt5.android.bindings.QtActivity; - -import io.highfidelity.oculus.OculusMobileActivity; - - -public class QuestQtActivity extends QtActivity { - private native void nativeOnCreate(); - private boolean launchedQuestMode = false; - - @Override - public void onCreate(Bundle savedInstanceState) { - Log.w("QQQ_Qt", "QuestQtActivity::onCreate"); - super.onCreate(savedInstanceState); - nativeOnCreate(); - } - - @Override - public void onDestroy() { - Log.w("QQQ_Qt", "QuestQtActivity::onDestroy"); - super.onDestroy(); - } - - public void launchOculusActivity() { - Log.w("QQQ_Qt", "QuestQtActivity::launchOculusActivity"); - runOnUiThread(()->{ - keepInterfaceRunning = true; - launchedQuestMode = true; - moveTaskToBack(true); - startActivity(new Intent(this, QuestRenderActivity.class)); - }); - } - - @Override - public void onResume() { - super.onResume(); - if (launchedQuestMode) { - moveTaskToBack(true); - } - } -} diff --git a/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java index a395a32b68..9e7c0ab973 100644 --- a/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java +++ b/android/apps/questFramePlayer/src/main/java/io/highfidelity/frameplayer/QuestRenderActivity.java @@ -1,14 +1,6 @@ package io.highfidelity.frameplayer; -import android.content.Intent; -import android.os.Bundle; - import io.highfidelity.oculus.OculusMobileActivity; public class QuestRenderActivity extends OculusMobileActivity { - @Override - public void onCreate(Bundle savedState) { - super.onCreate(savedState); - startActivity(new Intent(this, QuestQtActivity.class)); - } } diff --git a/android/apps/questInterface/CMakeLists.txt b/android/apps/questInterface/CMakeLists.txt new file mode 100644 index 0000000000..97ca46f6e5 --- /dev/null +++ b/android/apps/questInterface/CMakeLists.txt @@ -0,0 +1,16 @@ +set(TARGET_NAME questInterface) +setup_hifi_library() +link_hifi_libraries( + shared task networking qml + image fbx hfm render-utils physics entities octree + oculusMobile oculusMobilePlugin + gl gpu ${PLATFORM_GL_BACKEND} +) +target_opengl() +target_bullet() +target_oculus_mobile() + +add_subdirectory("${CMAKE_SOURCE_DIR}/interface" "libraries/interface") +include_directories("${CMAKE_SOURCE_DIR}/interface/src") +add_subdirectory("${CMAKE_SOURCE_DIR}/plugins/hifiCodec" "libraries/hifiCodecPlugin") +target_link_libraries(questInterface android log m interface) diff --git a/android/apps/questInterface/build.gradle b/android/apps/questInterface/build.gradle new file mode 100644 index 0000000000..3d13877607 --- /dev/null +++ b/android/apps/questInterface/build.gradle @@ -0,0 +1,149 @@ +import org.apache.tools.ant.taskdefs.condition.Os +apply plugin: 'com.android.application' + +task renameHifiACTaskDebug() { + doLast { + def sourceFile = new File("${appDir}/build/intermediates/cmake/debug/obj/arm64-v8a/","libhifiCodec.so") + def destinationFile = new File("${appDir}/src/main/jniLibs/arm64-v8a", "libplugins_libhifiCodec.so") + copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } + } +} +task renameHifiACTaskRelease(type: Copy) { + doLast { + def sourceFile = new File("${appDir}/build/intermediates/cmake/release/obj/arm64-v8a/","libhifiCodec.so") + def destinationFile = new File("${appDir}/src/main/jniLibs/arm64-v8a", "libplugins_libhifiCodec.so") + copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } + } +} + +android { + compileSdkVersion 28 + + defaultConfig { + applicationId "io.highfidelity.questInterface" + minSdkVersion 24 + targetSdkVersion 28 + versionCode 1 + versionName appVersionName + ndk { abiFilters 'arm64-v8a' } + externalNativeBuild { + cmake { + arguments '-DHIFI_ANDROID=1', + '-DHIFI_ANDROID_APP=questInterface', + '-DANDROID_TOOLCHAIN=clang', + '-DANDROID_STL=c++_shared', + '-DCMAKE_VERBOSE_MAKEFILE=ON', + '-DRELEASE_NUMBER=' + RELEASE_NUMBER, + '-DRELEASE_TYPE=' + RELEASE_TYPE, + '-DSTABLE_BUILD=' + STABLE_BUILD, + '-DDISABLE_QML=OFF', + '-DDISABLE_KTX_CACHE=OFF', + '-DUSE_BREAKPAD=OFF' + targets = ['questInterface'] + } + } + signingConfigs { + release { + storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null + storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : '' + keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : '' + keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : '' + v2SigningEnabled false + } + } + } + + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildTypes { + debug { + buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\"" + buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\"" + buildConfigField "String", "OAUTH_CLIENT_ID", "\"" + (System.getenv("OAUTH_CLIENT_ID") ? System.getenv("OAUTH_CLIENT_ID") : '') + "\"" + buildConfigField "String", "OAUTH_CLIENT_SECRET", "\"" + (System.getenv("OAUTH_CLIENT_SECRET") ? System.getenv("OAUTH_CLIENT_SECRET") : '') + "\"" + buildConfigField "String", "OAUTH_REDIRECT_URI", "\"" + (System.getenv("OAUTH_REDIRECT_URI") ? System.getenv("OAUTH_REDIRECT_URI") : '') + "\"" + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release + buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\"" + buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\"" + buildConfigField "String", "OAUTH_CLIENT_ID", "\"" + (System.getenv("OAUTH_CLIENT_ID") ? System.getenv("OAUTH_CLIENT_ID") : '') + "\"" + buildConfigField "String", "OAUTH_CLIENT_SECRET", "\"" + (System.getenv("OAUTH_CLIENT_SECRET") ? System.getenv("OAUTH_CLIENT_SECRET") : '') + "\"" + buildConfigField "String", "OAUTH_REDIRECT_URI", "\"" + (System.getenv("OAUTH_REDIRECT_URI") ? System.getenv("OAUTH_REDIRECT_URI") : '') + "\"" + } + } + + externalNativeBuild { + cmake { + path '../../../CMakeLists.txt' + } + } + + applicationVariants.all { variant -> + // Our asset contents depend on items produced in the CMake build + // so our merge has to depend on the external native build + variant.externalNativeBuildTasks.each { task -> + variant.mergeResources.dependsOn(task) + if (Os.isFamily(Os.FAMILY_UNIX)) { + // FIXME + def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${variant.name.capitalize()}", false).first() + def runDumpSymsTask = rootProject.getTasksByName("runBreakpadDumpSyms${variant.name.capitalize()}", false).first() + def renameHifiACTask = rootProject.getTasksByName("renameHifiACTask${variant.name.capitalize()}", false).first() + runDumpSymsTask.dependsOn(task) + variant.assemble.dependsOn(uploadDumpSymsTask) + variant.mergeResources.dependsOn(renameHifiACTask) + } + } + + variant.mergeAssets.doLast { + def assetList = new LinkedList() + def youngestLastModified = 0 + + // Copy the compiled resources generated by the external native build + copy { + from new File(projectDir, "../../../interface/compiledResources") + into outputDir + duplicatesStrategy DuplicatesStrategy.INCLUDE + eachFile { details -> + youngestLastModified = Math.max(youngestLastModified, details.lastModified) + assetList.add(details.path) + } + } + + // Copy the scripts directory + copy { + from new File(projectDir, "../../../scripts") + into new File(outputDir, "scripts") + duplicatesStrategy DuplicatesStrategy.INCLUDE + eachFile { details-> + youngestLastModified = Math.max(youngestLastModified, details.lastModified) + assetList.add("scripts/" + details.path) + } + } + + // Write a list of files to be unpacked to the cache folder + new File(outputDir, 'cache_assets.txt').withWriter { out -> + out.println(Long.toString(youngestLastModified)) + assetList.each { file -> out.println(file) } + } + } + + variant.outputs.all { + if (RELEASE_NUMBER != '0') { + outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk" + } + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs') + implementation project(':oculus') + implementation project(':qt') +} diff --git a/android/apps/questInterface/proguard-rules.pro b/android/apps/questInterface/proguard-rules.pro new file mode 100644 index 0000000000..b3c0078513 --- /dev/null +++ b/android/apps/questInterface/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Android\SDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/android/apps/questInterface/src/main/AndroidManifest.xml b/android/apps/questInterface/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..a5de47bdce --- /dev/null +++ b/android/apps/questInterface/src/main/AndroidManifest.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/apps/questInterface/src/main/cpp/native.cpp b/android/apps/questInterface/src/main/cpp/native.cpp new file mode 100644 index 0000000000..3c1563c93d --- /dev/null +++ b/android/apps/questInterface/src/main/cpp/native.cpp @@ -0,0 +1,106 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +void initOculusPlatform(JNIEnv* env, jobject obj) { + static std::once_flag once; + std::call_once(once, [&]{ + // static const char* appID = "2343652845669354"; + // if (ovr_PlatformInitializeAndroid(appID, obj, env) != ovrPlatformInitialize_Success) { + // __android_log_write(ANDROID_LOG_WARN, "QQQ", "Failed to init platform SDK"); + // return; + // } + // ovr_Voip_SetSystemVoipSuppressed(true); + }); +} + +void getClassName(JNIEnv *env, jobject obj){ + jclass cls = env->GetObjectClass(obj); + jmethodID mid = env->GetMethodID(cls,"getClass", "()Ljava/lang/Class;"); + jobject clsObj = env->CallObjectMethod(obj, mid); + + cls= env->GetObjectClass(clsObj); + + mid= env->GetMethodID(cls, "getName", "()Ljava/lang/String;"); + + jstring strObj = (jstring) env->CallObjectMethod(clsObj, mid); + + const char* str = env->GetStringUTFChars(strObj, NULL); + + __android_log_print(ANDROID_LOG_ERROR,__FUNCTION__, "Native Class call: %s",str); + + env->ReleaseStringUTFChars(strObj, str); +} + + +extern "C" { + JNIEXPORT void JNICALL + Java_io_highfidelity_oculus_OculusMobileActivity_nativeInitOculusPlatform(JNIEnv *env, jobject obj){ + initOculusPlatform(env, obj); + } +QAndroidJniObject __interfaceActivity; + + JNIEXPORT void JNICALL + Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnCreate(JNIEnv *env, jobject obj) { + __android_log_print(ANDROID_LOG_INFO, "QQQ", __FUNCTION__); + initOculusPlatform(env, obj); + getClassName(env, obj); + + __interfaceActivity = QAndroidJniObject(obj); + + QObject::connect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, []() { + __interfaceActivity.callMethod("onAppLoadedComplete", "()V"); + + QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, + nullptr, + nullptr); + }); + } + + + +JNIEXPORT void Java_io_highfidelity_oculus_OculusMobileActivity_questOnAppAfterLoad(JNIEnv* env, jobject obj) { + AndroidHelper::instance().moveToThread(qApp->thread()); +} + + JNIEXPORT void JNICALL + Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnPause(JNIEnv *env, jobject obj) { + AndroidHelper::instance().notifyEnterBackground(); + } + + JNIEXPORT void JNICALL + Java_io_highfidelity_oculus_OculusMobileActivity_questNativeOnResume(JNIEnv *env, jobject obj) { + AndroidHelper::instance().notifyEnterForeground(); + } + + JNIEXPORT void JNICALL + Java_io_highfidelity_questInterface_receiver_HeadsetStateReceiver_notifyHeadsetOn(JNIEnv *env, + jobject instance, + jboolean pluggedIn) { + AndroidHelper::instance().notifyHeadsetOn(pluggedIn); + } + +} diff --git a/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java b/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java new file mode 100644 index 0000000000..d55f97ad49 --- /dev/null +++ b/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/InterfaceActivity.java @@ -0,0 +1,13 @@ +package io.highfidelity.questInterface; + +import android.os.Bundle; +import io.highfidelity.oculus.OculusMobileActivity; +import io.highfidelity.utils.HifiUtils; + +public class InterfaceActivity extends OculusMobileActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + HifiUtils.upackAssets(getAssets(), getCacheDir().getAbsolutePath()); + super.onCreate(savedInstanceState); + } +} diff --git a/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/PermissionsChecker.java b/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/PermissionsChecker.java new file mode 100644 index 0000000000..154435fcaf --- /dev/null +++ b/android/apps/questInterface/src/main/java/io/highfidelity/questInterface/PermissionsChecker.java @@ -0,0 +1,68 @@ +package io.highfidelity.questInterface; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import io.highfidelity.oculus.OculusMobileActivity; +import io.highfidelity.utils.HifiUtils; + +public class PermissionsChecker extends Activity { + private static final int REQUEST_PERMISSIONS = 20; + private static final String TAG = PermissionsChecker.class.getName(); + private static final String[] REQUIRED_PERMISSIONS = new String[]{ + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.RECORD_AUDIO, + Manifest.permission.CAMERA + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestAppPermissions(REQUIRED_PERMISSIONS,REQUEST_PERMISSIONS); + } + + public void requestAppPermissions(final String[] requestedPermissions, + final int requestCode) { + int permissionCheck = PackageManager.PERMISSION_GRANTED; + boolean shouldShowRequestPermissionRationale = false; + for (String permission : requestedPermissions) { + permissionCheck = permissionCheck + checkSelfPermission(permission); + shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale || shouldShowRequestPermissionRationale(permission); + } + if (permissionCheck != PackageManager.PERMISSION_GRANTED) { + System.out.println("Permission was not granted. Ask for permissions"); + if (shouldShowRequestPermissionRationale) { + requestPermissions(requestedPermissions, requestCode); + } else { + requestPermissions(requestedPermissions, requestCode); + } + } else { + System.out.println("Launching the other activity.."); + launchActivityWithPermissions(); + } + } + + private void launchActivityWithPermissions() { + startActivity(new Intent(this, InterfaceActivity.class)); + finish(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + int permissionCheck = PackageManager.PERMISSION_GRANTED; + for (int permission : grantResults) { + permissionCheck = permissionCheck + permission; + } + if ((grantResults.length > 0) && permissionCheck == PackageManager.PERMISSION_GRANTED) { + launchActivityWithPermissions(); + } else if (grantResults.length > 0) { + System.out.println("User has deliberately denied Permissions. Launching anyways"); + launchActivityWithPermissions(); + } + } +} diff --git a/android/apps/questInterface/src/main/res/drawable/ic_launcher.xml b/android/apps/questInterface/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 0000000000..03b1edc4e9 --- /dev/null +++ b/android/apps/questInterface/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/android/apps/questInterface/src/main/res/values/strings.xml b/android/apps/questInterface/src/main/res/values/strings.xml new file mode 100644 index 0000000000..99e8d501ac --- /dev/null +++ b/android/apps/questInterface/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Interface + diff --git a/android/build.gradle b/android/build.gradle index ed2ca1c47e..5a4dbc0033 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -42,6 +42,8 @@ ext { RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV' STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0' EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : '' + appVersionCode = Integer.valueOf(VERSION_CODE ?: 1) + appVersionName = RELEASE_NUMBER ?: "1.0" } def appDir = new File(projectDir, 'apps/interface') diff --git a/android/gradle.properties b/android/gradle.properties index ac639c5ae7..4236282c8b 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1 +1,2 @@ org.gradle.jvmargs=-Xms2g -Xmx4g +android.debug.obsoleteApi=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 36dba9b2f5..4c0f3c1035 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Dec 01 08:32:47 PST 2018 +#Wed Dec 19 13:46:46 PST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip diff --git a/android/libraries/oculus/build.gradle b/android/libraries/oculus/build.gradle index b072f99eb7..f31efcfe95 100644 --- a/android/libraries/oculus/build.gradle +++ b/android/libraries/oculus/build.gradle @@ -15,3 +15,7 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } } + +dependencies { + implementation project(path: ':qt') +} diff --git a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java index 01d74ea94d..9ab07bb4dd 100644 --- a/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java +++ b/android/libraries/oculus/src/main/java/io/highfidelity/oculus/OculusMobileActivity.java @@ -7,62 +7,65 @@ // package io.highfidelity.oculus; -import android.app.Activity; -import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; -import android.view.WindowManager; + +import org.qtproject.qt5.android.bindings.QtActivity; +import io.highfidelity.utils.HifiUtils; /** * Contains a native surface and forwards the activity lifecycle and surface lifecycle * events to the OculusMobileDisplayPlugin */ -public class OculusMobileActivity extends Activity implements SurfaceHolder.Callback { +public class OculusMobileActivity extends QtActivity implements SurfaceHolder.Callback { private static final String TAG = OculusMobileActivity.class.getSimpleName(); static { System.loadLibrary("oculusMobile"); } + private native void nativeOnCreate(); private native static void nativeOnResume(); private native static void nativeOnPause(); - private native static void nativeOnDestroy(); private native static void nativeOnSurfaceChanged(Surface s); + private native void questNativeOnCreate(); + private native void questNativeOnPause(); + private native void questNativeOnResume(); + private native void questOnAppAfterLoad(); + + private SurfaceView mView; private SurfaceHolder mSurfaceHolder; - - public static void launch(Activity activity) { - if (activity != null) { - activity.runOnUiThread(()->{ - activity.startActivity(new Intent(activity, OculusMobileActivity.class)); - }); - } - } - - @Override public void onCreate(Bundle savedInstanceState) { - Log.w(TAG, "QQQ onCreate"); super.onCreate(savedInstanceState); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + Log.w(TAG, "QQQ onCreate"); // Create a native surface for VR rendering (Qt GL surfaces are not suitable // because of the lack of fine control over the surface callbacks) + // Forward the create message to the JNI code mView = new SurfaceView(this); - setContentView(mView); mView.getHolder().addCallback(this); - // Forward the create message to the JNI code nativeOnCreate(); + questNativeOnCreate(); + } + public void onAppLoadedComplete() { + Log.w(TAG, "QQQ Load Completed"); + runOnUiThread(() -> { + setContentView(mView); + questOnAppAfterLoad(); + }); } @Override protected void onDestroy() { Log.w(TAG, "QQQ onDestroy"); - if (mSurfaceHolder != null) { - nativeOnSurfaceChanged(null); - } - nativeOnDestroy(); + + nativeOnSurfaceChanged(null); + + Log.w(TAG, "QQQ onDestroy -- SUPER onDestroy"); super.onDestroy(); } @@ -70,19 +73,38 @@ public class OculusMobileActivity extends Activity implements SurfaceHolder.Call protected void onResume() { Log.w(TAG, "QQQ onResume"); super.onResume(); + //Reconnect the global reference back to handler + nativeOnCreate(); + + questNativeOnResume(); nativeOnResume(); } @Override protected void onPause() { Log.w(TAG, "QQQ onPause"); - nativeOnPause(); super.onPause(); + + questNativeOnPause(); + nativeOnPause(); + } + + @Override + protected void onStop(){ + super.onStop(); + Log.w(TAG, "QQQ Onstop called"); + } + + @Override + protected void onRestart(){ + super.onRestart(); + Log.w(TAG, "QQQ onRestart called ****"); + questOnAppAfterLoad(); } @Override public void surfaceCreated(SurfaceHolder holder) { - Log.w(TAG, "QQQ surfaceCreated"); + Log.w(TAG, "QQQ surfaceCreated ************************************"); nativeOnSurfaceChanged(holder.getSurface()); mSurfaceHolder = holder; } @@ -96,8 +118,9 @@ public class OculusMobileActivity extends Activity implements SurfaceHolder.Call @Override public void surfaceDestroyed(SurfaceHolder holder) { - Log.w(TAG, "QQQ surfaceDestroyed"); + Log.w(TAG, "QQQ surfaceDestroyed ***************************************************"); nativeOnSurfaceChanged(null); mSurfaceHolder = null; + } } \ No newline at end of file diff --git a/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java index 6a6688ac41..46f2af46e7 100644 --- a/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java +++ b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java @@ -70,9 +70,6 @@ public class QtActivity extends Activity { public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme. private QtActivityLoader m_loader = new QtActivityLoader(this); - public boolean isLoading; - public boolean keepInterfaceRunning; - public QtActivity() { } @@ -229,10 +226,13 @@ public class QtActivity extends Activity { //--------------------------------------------------------------------------- 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); } @@ -364,7 +364,10 @@ public class QtActivity extends Activity { @Override protected void onDestroy() { super.onDestroy(); - QtApplication.invokeDelegate(); + + QtNative.terminateQt(); + QtNative.setActivity(null,null); + System.exit(0); } //--------------------------------------------------------------------------- @@ -506,9 +509,9 @@ public class QtActivity extends Activity { super.onPause(); // GC: this trick allow us to show a splash activity until Qt app finishes // loading - if (!isLoading && !keepInterfaceRunning) { - QtApplication.invokeDelegate(); - } + //QtApplication.invokeDelegate(); + + //TODO(Amer): looking into why this messes up pause. } //--------------------------------------------------------------------------- @@ -647,11 +650,7 @@ public class QtActivity extends Activity { @Override protected void onStop() { super.onStop(); - if (!keepInterfaceRunning) { - QtApplication.invokeDelegate(); - } - QtNative.terminateQt(); - QtNative.setActivity(null,null); + QtApplication.invokeDelegate(); } //--------------------------------------------------------------------------- diff --git a/android/settings.gradle b/android/settings.gradle index 699f617cce..c7b70cfde2 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -12,15 +12,26 @@ project(':qt').projectDir = new File(settingsDir, 'libraries/qt') // Applications // -include ':interface' -project(':interface').projectDir = new File(settingsDir, 'apps/interface') +if (!getSettings().hasProperty("SUPPRESS_INTERFACE")) { + include ':interface' + project(':interface').projectDir = new File(settingsDir, 'apps/interface') +} + +if (!getSettings().hasProperty("SUPPRESS_QUEST_INTERFACE")) { + include ':questInterface' + project(':questInterface').projectDir = new File(settingsDir, 'apps/questInterface') +} // // Test projects // -include ':framePlayer' -project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer') +if (!getSettings().hasProperty("SUPPRESS_FRAME_PLAYER")) { + include ':framePlayer' + project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer') +} -include ':questFramePlayer' -project(':questFramePlayer').projectDir = new File(settingsDir, 'apps/questFramePlayer') +if (!getSettings().hasProperty("SUPPRESS_QUEST_FRAME_PLAYER")) { + include ':questFramePlayer' + project(':questFramePlayer').projectDir = new File(settingsDir, 'apps/questFramePlayer') +} diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index 64fbcc4ea6..c5bb2b4054 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -270,6 +270,16 @@ macro(AUTOSCRIBE_SHADER_LIBS) set(AUTOSCRIBE_SHADERGEN_COMMANDS_FILE ${CMAKE_CURRENT_BINARY_DIR}/shadergen.txt) file(WRITE ${AUTOSCRIBE_SHADERGEN_COMMANDS_FILE} "${AUTOSCRIBE_SHADERGEN_COMMANDS}") + if (HIFI_ANDROID) + if ( + (${HIFI_ANDROID_APP} STREQUAL "questInterface") OR + (${HIFI_ANDROID_APP} STREQUAL "questFramePlayer") OR + (${HIFI_ANDROID_APP} STREQUAL "framePlayer") + ) + set(EXTRA_SHADERGEN_ARGS --extensions EXT_clip_cull_distance) + endif() + endif() + # A custom python script which will generate all our shader artifacts add_custom_command( OUTPUT ${SCRIBED_SHADERS} ${SPIRV_SHADERS} ${REFLECTED_SHADERS} @@ -279,6 +289,7 @@ macro(AUTOSCRIBE_SHADER_LIBS) --tools-dir ${VCPKG_TOOLS_DIR} --build-dir ${CMAKE_CURRENT_BINARY_DIR} --source-dir ${CMAKE_SOURCE_DIR} + ${EXTRA_SHADERGEN_ARGS} DEPENDS ${AUTOSCRIBE_SHADER_HEADERS} ${CMAKE_SOURCE_DIR}/tools/shadergen.py ${ALL_SCRIBE_SHADERS}) add_custom_target(shadergen DEPENDS ${SCRIBED_SHADERS} ${SPIRV_SHADERS} ${REFLECTED_SHADERS}) diff --git a/hifi_android.py b/hifi_android.py index 2e6a42d127..b8a606a82f 100644 --- a/hifi_android.py +++ b/hifi_android.py @@ -53,9 +53,9 @@ ANDROID_PACKAGES = { 'includeLibs': ['libvrapi.so'] }, 'oculusPlatform': { - 'file': 'OVRPlatformSDK_v1.32.0.zip', - 'versionId': 'jG9DB16zOGxSrmtZy4jcQnwO0TJUuaeL', - 'checksum': 'ab5b203b3a39a56ab148d68fff769e05', + 'file': 'OVRPlatformSDK_v1.34.0.zip', + 'versionId': 'vbRUkkyzUAXfTGSEtuiUr_7.Fm5h5BZk', + 'checksum': '16e4c5f39520f122bc49cb6d5bb88289', 'sharedLibFolder': 'Android/libs/arm64-v8a', 'includeLibs': ['libovrplatformloader.so'] }, diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index a1b89e1529..9a9252112c 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -425,11 +425,12 @@ FocusScope { console.warn("Could not find top level window for " + item); return; } - +/* if (typeof Controller === "undefined") { console.warn("Controller not yet available... can't center"); return; } +*/ var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect(); var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, @@ -455,15 +456,17 @@ FocusScope { console.warn("Could not find top level window for " + item); return; } - +/* if (typeof Controller === "undefined") { console.warn("Controller not yet available... can't reposition targetWindow:" + targetWindow); return; } +*/ var oldRecommendedRect = recommendedRect; var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; - var newRecommendedRect = Controller.getRecommendedHUDRect(); + var newRecommendedRect = { width: 1280, height: 720, x: 0, y: 0 }; + if (typeof Controller !== "undefined") newRecommendedRect = Controller.getRecommendedHUDRect(); var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; repositionWindow(targetWindow, false, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); } @@ -480,7 +483,8 @@ FocusScope { return; } - var recommended = Controller.getRecommendedHUDRect(); + var recommended = { width: 1280, height: 720, x: 0, y: 0 }; + if (typeof Controller !== "undefined") recommended = Controller.getRecommendedHUDRect(); var maxX = recommended.x + recommended.width; var maxY = recommended.y + recommended.height; var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y); diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index c44ebdbab1..a97d94d91c 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -18,8 +18,8 @@ OriginalDesktop.Desktop { hoverEnabled: true propagateComposedEvents: true scrollGestureEnabled: false // we don't need/want these - onEntered: ApplicationCompositor.reticleOverDesktop = true - onExited: ApplicationCompositor.reticleOverDesktop = false + onEntered: if (typeof ApplicationCompositor !== "undefined") ApplicationCompositor.reticleOverDesktop = true + onExited: if (typeof ApplicationCompositor !== "undefined") ApplicationCompositor.reticleOverDesktop = false acceptedButtons: Qt.NoButton } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bcd07106ca..a611738445 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + #include "Application.h" #include @@ -37,7 +38,6 @@ #include #include - #include #include #include @@ -51,6 +51,7 @@ #include #include + #include #include #include @@ -191,6 +192,9 @@ #include "scripting/WalletScriptingInterface.h" #include "scripting/TTSScriptingInterface.h" #include "scripting/KeyboardScriptingInterface.h" + + + #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" #endif @@ -239,6 +243,7 @@ #include "webbrowser/WebBrowserSuggestionsEngine.h" #include + #include "AboutUtil.h" #if defined(Q_OS_WIN) @@ -1745,7 +1750,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); _applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float { #if defined(Q_OS_ANDROID) - return 1; + return 1 ; #else return 0; #endif @@ -1848,6 +1853,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo this->installEventFilter(this); + + #ifdef HAVE_DDE auto ddeTracker = DependencyManager::get(); ddeTracker->init(); @@ -3085,7 +3092,7 @@ void Application::initializeUi() { } if (TouchscreenVirtualPadDevice::NAME == inputPlugin->getName()) { _touchscreenVirtualPadDevice = std::dynamic_pointer_cast(inputPlugin); -#if defined(Q_OS_ANDROID) +#if defined(ANDROID_APP_INTERFACE) auto& virtualPadManager = VirtualPad::Manager::instance(); connect(&virtualPadManager, &VirtualPad::Manager::hapticFeedbackRequested, this, [](int duration) { @@ -3617,10 +3624,14 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { } // Get controller availability +#ifdef ANDROID_APP_QUEST_INTERFACE + bool hasHandControllers = true; +#else bool hasHandControllers = false; if (PluginUtils::isViveControllerAvailable() || PluginUtils::isOculusTouchControllerAvailable()) { hasHandControllers = true; } +#endif // Check HMD use (may be technically available without being in use) bool hasHMD = PluginUtils::isHMDAvailable(); @@ -8237,6 +8248,7 @@ void Application::loadDomainConnectionDialog() { } void Application::toggleLogDialog() { +#ifndef ANDROID_APP_QUEST_INTERFACE if (getLoginDialogPoppedUp()) { return; } @@ -8260,6 +8272,7 @@ void Application::toggleLogDialog() { } else { _logDialog->show(); } +#endif } void Application::recreateLogWindow(int keepOnTop) { @@ -9125,17 +9138,23 @@ void Application::beforeEnterBackground() { void Application::enterBackground() { QMetaObject::invokeMethod(DependencyManager::get().data(), "stop", Qt::BlockingQueuedConnection); +// Quest only supports one plugin which can't be deactivated currently +#if !defined(ANDROID_APP_QUEST_INTERFACE) if (getActiveDisplayPlugin()->isActive()) { getActiveDisplayPlugin()->deactivate(); } +#endif } void Application::enterForeground() { QMetaObject::invokeMethod(DependencyManager::get().data(), "start", Qt::BlockingQueuedConnection); +// Quest only supports one plugin which can't be deactivated currently +#if !defined(ANDROID_APP_QUEST_INTERFACE) if (!getActiveDisplayPlugin() || getActiveDisplayPlugin()->isActive() || !getActiveDisplayPlugin()->activate()) { qWarning() << "Could not re-activate display plugin"; } +#endif auto nodeList = DependencyManager::get(); nodeList->setSendDomainServerCheckInEnabled(true); } diff --git a/libraries/gpu/src/gpu/FrameReader.cpp b/libraries/gpu/src/gpu/FrameReader.cpp index 6e39a38097..2fe143ee90 100644 --- a/libraries/gpu/src/gpu/FrameReader.cpp +++ b/libraries/gpu/src/gpu/FrameReader.cpp @@ -388,10 +388,23 @@ ShaderPointer Deserializer::readShader(const json& node) { return nullptr; } + static std::map shadersIdsByName; + if (shadersIdsByName.empty()) { + for (const auto id : shader::allShaders()) { + const auto& shaderSource = shader::Source::get(id); + shadersIdsByName[shaderSource.name] = id; + } + } + // FIXME support procedural shaders Shader::Type type = node[keys::type]; std::string name = node[keys::name]; - uint32_t id = node[keys::id]; + // Using the serialized ID is bad, because it's generated at + // cmake time, and can change across platforms or when + // shaders are added or removed + // uint32_t id = node[keys::id]; + + uint32_t id = shadersIdsByName[name]; ShaderPointer result; switch (type) { //case Shader::Type::GEOMETRY: diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index 43205ba4c2..3015de7e0e 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -167,7 +167,7 @@ TransformObject getTransformObject() { vec4 eyeClipEdge[2]= vec4[2](vec4(-1,0,0,1), vec4(1,0,0,1)); vec2 eyeOffsetScale = vec2(-0.5, +0.5); uint eyeIndex = uint(_stereoSide); -#ifndef GPU_GLES +#if !defined(GPU_GLES) || (defined(HAVE_EXT_clip_cull_distance) && !defined(VULKAN)) gl_ClipDistance[0] = dot(<$clipPos$>, eyeClipEdge[eyeIndex]); #endif float newClipPosX = <$clipPos$>.x * 0.5 + eyeOffsetScale[eyeIndex] * <$clipPos$>.w; diff --git a/libraries/oculusMobile/src/ovr/VrHandler.cpp b/libraries/oculusMobile/src/ovr/VrHandler.cpp index de2b4e1ff6..b3b1416785 100644 --- a/libraries/oculusMobile/src/ovr/VrHandler.cpp +++ b/libraries/oculusMobile/src/ovr/VrHandler.cpp @@ -315,10 +315,6 @@ JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOn SURFACE.onCreate(env, obj); } -JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnDestroy(JNIEnv*, jclass) { - __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); -} - JNIEXPORT void JNICALL Java_io_highfidelity_oculus_OculusMobileActivity_nativeOnResume(JNIEnv*, jclass) { __android_log_write(ANDROID_LOG_WARN, "QQQ_JNI", __FUNCTION__); SURFACE.setResumed(true); diff --git a/libraries/render-utils/src/parabola_forward.slv b/libraries/render-utils/src/parabola_forward.slv new file mode 100644 index 0000000000..4eb1456666 --- /dev/null +++ b/libraries/render-utils/src/parabola_forward.slv @@ -0,0 +1,7 @@ +layout(location=0) in vec4 _color; + +layout(location=0) out vec4 _fragColor0; + +void main(void) { + _fragColor0 = _color; +} \ No newline at end of file diff --git a/libraries/shaders/headers/310es/header.glsl b/libraries/shaders/headers/310es/header.glsl index 9a0af85281..a21b2ec5d6 100644 --- a/libraries/shaders/headers/310es/header.glsl +++ b/libraries/shaders/headers/310es/header.glsl @@ -1,6 +1,3 @@ -#version 310 es -#define GPU_GLES -#define GPU_GLES_310 #define BITFIELD highp int #define LAYOUT(X) layout(X) #define LAYOUT_STD140(X) layout(std140, X) @@ -9,6 +6,9 @@ #define gl_VertexID gl_VertexIndex #endif #extension GL_EXT_texture_buffer : enable +#if defined(HAVE_EXT_clip_cull_distance) && !defined(VULKAN) +#extension GL_EXT_clip_cull_distance : enable +#endif precision highp float; precision highp samplerBuffer; precision highp sampler2DShadow; diff --git a/libraries/shaders/headers/310es/version.glsl b/libraries/shaders/headers/310es/version.glsl new file mode 100644 index 0000000000..52e5edea8b --- /dev/null +++ b/libraries/shaders/headers/310es/version.glsl @@ -0,0 +1,3 @@ +#version 310 es +#define GPU_GLES +#define GPU_GLES_310 diff --git a/libraries/shaders/headers/410/header.glsl b/libraries/shaders/headers/410/header.glsl index 901ae6f9db..20bd6b2505 100644 --- a/libraries/shaders/headers/410/header.glsl +++ b/libraries/shaders/headers/410/header.glsl @@ -1,5 +1,3 @@ -#version 410 core -#define GPU_GL410 #define BITFIELD int #if defined(VULKAN) #extension GL_ARB_shading_language_420pack : require diff --git a/libraries/shaders/headers/410/version.glsl b/libraries/shaders/headers/410/version.glsl new file mode 100644 index 0000000000..01ab7e5398 --- /dev/null +++ b/libraries/shaders/headers/410/version.glsl @@ -0,0 +1,2 @@ +#version 410 core +#define GPU_GL410 diff --git a/libraries/shaders/headers/450/header.glsl b/libraries/shaders/headers/450/header.glsl index 6ce61b4378..ef0ec09414 100644 --- a/libraries/shaders/headers/450/header.glsl +++ b/libraries/shaders/headers/450/header.glsl @@ -1,5 +1,3 @@ -#version 450 core -#define GPU_GL450 #define GPU_SSBO_TRANSFORM_OBJECT #define BITFIELD int #define LAYOUT(X) layout(X) diff --git a/libraries/shaders/headers/450/version.glsl b/libraries/shaders/headers/450/version.glsl new file mode 100644 index 0000000000..874d578bd9 --- /dev/null +++ b/libraries/shaders/headers/450/version.glsl @@ -0,0 +1,2 @@ +#version 450 core +#define GPU_GL450 diff --git a/libraries/shaders/src/shaders/Shaders.cpp b/libraries/shaders/src/shaders/Shaders.cpp index c7e39b2940..a074c2f0c9 100644 --- a/libraries/shaders/src/shaders/Shaders.cpp +++ b/libraries/shaders/src/shaders/Shaders.cpp @@ -224,7 +224,7 @@ String Source::getSource(Dialect dialect, Variant variant) const { } } -#ifdef Q_OS_ANDROID +#if defined(Q_OS_ANDROID) || defined(USE_GLES) // SPIRV cross injects "#extension GL_OES_texture_buffer : require" into the GLSL shaders, // which breaks android rendering return variantSource.scribe; diff --git a/libraries/ui/src/QmlFragmentClass.cpp b/libraries/ui/src/QmlFragmentClass.cpp index 13e3527ded..fbd045fdb1 100644 --- a/libraries/ui/src/QmlFragmentClass.cpp +++ b/libraries/ui/src/QmlFragmentClass.cpp @@ -24,7 +24,7 @@ QmlFragmentClass::QmlFragmentClass(bool restricted, QString id) : QmlWindowClass // Method called by Qt scripts to create a new bottom menu bar in Android QScriptValue QmlFragmentClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { - +#ifndef DISABLE_QML std::lock_guard guard(_mutex); auto qml = context->argument(0).toVariant().toMap().value("qml"); if (qml.isValid()) { @@ -53,6 +53,9 @@ QScriptValue QmlFragmentClass::internal_constructor(QScriptContext* context, QSc QScriptValue scriptObject = engine->newQObject(retVal); _fragments[qml.toString()] = scriptObject; return scriptObject; +#else + return QScriptValue(); +#endif } void QmlFragmentClass::close() { @@ -61,6 +64,7 @@ void QmlFragmentClass::close() { } QObject* QmlFragmentClass::addButton(const QVariant& properties) { +#ifndef DISABLE_QML QVariant resultVar; Qt::ConnectionType connectionType = Qt::AutoConnection; @@ -79,8 +83,10 @@ QObject* QmlFragmentClass::addButton(const QVariant& properties) { qWarning() << "QmlFragmentClass addButton result not a QObject"; return NULL; } - return qmlButton; +#else + return nullptr; +#endif } void QmlFragmentClass::removeButton(QObject* button) { diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 0182e3adc3..1140dbb079 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -98,6 +98,7 @@ QmlWindowClass::QmlWindowClass(bool restricted) : _restricted(restricted) { * @property {boolean} visible */ void QmlWindowClass::initQml(QVariantMap properties) { +#ifndef DISABLE_QML auto offscreenUi = DependencyManager::get(); _source = properties[SOURCE_PROPERTY].toString(); @@ -150,6 +151,7 @@ void QmlWindowClass::initQml(QVariantMap properties) { Q_ASSERT(_qmlWindow); Q_ASSERT(dynamic_cast(_qmlWindow.data())); +#endif } void QmlWindowClass::qmlToScript(const QVariant& message) { diff --git a/prebuild.py b/prebuild.py index 060e1fd3b0..5325ca34bc 100644 --- a/prebuild.py +++ b/prebuild.py @@ -58,6 +58,9 @@ logging.setLoggerClass(TrackableLogger) logger = logging.getLogger('prebuild') def headSha(): + if shutil.which('git') is None: + logger.warn("Unable to find git executable, can't caclulate commit ID") + return '0xDEADBEEF' repo_dir = os.path.dirname(os.path.abspath(__file__)) git = subprocess.Popen( 'git rev-parse --short HEAD', @@ -67,7 +70,7 @@ def headSha(): stdout, _ = git.communicate() sha = stdout.split('\n')[0] if not sha: - raise RuntimeError("couldn't find git sha") + raise RuntimeError("couldn't find git sha for repository {}".format(repo_dir)) return sha @contextmanager diff --git a/tools/shadergen.py b/tools/shadergen.py index ffbe1662ec..f82b471f17 100644 --- a/tools/shadergen.py +++ b/tools/shadergen.py @@ -49,11 +49,36 @@ def getCommonScribeArgs(scribefile, includeLibs): scribeArgs.append(scribefile) return scribeArgs -def getDialectAndVariantHeaders(dialect, variant): +extensionsHeaderMutex = Lock() + +def getExtensionsHeader(dialect, variant, extensions): + extensionHeader = '{}/extensions_{}_{}.glsl'.format(args.build_dir, dialect, variant) + global extensionsHeaderMutex + extensionsHeaderMutex.acquire() + if not os.path.exists(extensionHeader): + extensionsDefines = [] + for extension in extensions: + extensionsDefines.append('#define HAVE_{}'.format(extension)) + # make sure we end with a line feed + extensionsDefines.append("\r\n") + with open(extensionHeader, "w") as f: + f.write('\r\n'.join(extensionsDefines)) + extensionsHeaderMutex.release() + return extensionHeader + + +def getDialectAndVariantHeaders(dialect, variant, extensions=None): + result = [] headerPath = args.source_dir + '/libraries/shaders/headers/' - variantHeader = headerPath + ('stereo.glsl' if (variant == 'stereo') else 'mono.glsl') + versionHeader = headerPath + dialect + '/version.glsl' + result.append(versionHeader) + if extensions is not None: + result.append(getExtensionsHeader(dialect, variant, extensions)) dialectHeader = headerPath + dialect + '/header.glsl' - return [dialectHeader, variantHeader] + result.append(dialectHeader) + variantHeader = headerPath + ('stereo.glsl' if (variant == 'stereo') else 'mono.glsl') + result.append(variantHeader) + return result class ScribeDependenciesCache: cache = {} @@ -170,7 +195,7 @@ def processCommand(line): scribeDepCache.gen(scribeFile, libs, dialect, variant) scribeArgs = getCommonScribeArgs(scribeFile, libs) - for header in getDialectAndVariantHeaders(dialect, variant): + for header in getDialectAndVariantHeaders(dialect, variant, args.extensions): scribeArgs.extend(['-H', header]) scribeArgs.extend(['-o', unoptGlslFile]) executeSubprocess(scribeArgs) @@ -218,6 +243,7 @@ def main(): parser = ArgumentParser(description='Generate shader artifacts.') +parser.add_argument('--extensions', type=str, nargs='*', help='Available extensions for the shaders') parser.add_argument('--commands', type=argparse.FileType('r'), help='list of commands to execute') parser.add_argument('--tools-dir', type=str, help='location of the host compatible binaries') parser.add_argument('--build-dir', type=str, help='The build directory base path') @@ -230,8 +256,8 @@ args = None if len(sys.argv) == 1: # for debugging sourceDir = expanduser('~/git/hifi') - toolsDir = os.path.join(expanduser('~/git/vcpkg'), 'installed', 'x64-windows', 'tools') - buildPath = sourceDir + '/build' + toolsDir = 'd:/hifi/vcpkg/android/fd82f0a8/installed/x64-windows/tools' + buildPath = sourceDir + '/build_android' commandsPath = buildPath + '/libraries/shaders/shadergen.txt' shaderDir = buildPath + '/libraries/shaders' testArgs = '--commands {} --tools-dir {} --build-dir {} --source-dir {}'.format( @@ -239,6 +265,7 @@ if len(sys.argv) == 1: ).split() testArgs.append('--debug') testArgs.append('--force') + testArgs.extend('--extensions EXT_clip_cull_distance'.split()) #testArgs.append('--dry-run') args = parser.parse_args(testArgs) else: