Merge branch 'master' of https://github.com/highfidelity/hifi into project-freeloco

This commit is contained in:
r3tk0n 2019-02-19 12:02:50 -08:00
commit ebbf48a11b
1002 changed files with 32800 additions and 23615 deletions

5
.gitignore vendored
View file

@ -14,11 +14,13 @@ Makefile
# Android Studio
*.iml
*.class
local.properties
android/gradle*
android/.gradle
android/**/src/main/jniLibs
android/**/libs
android/**/bin
android/**/src/main/res/values/libs.xml
android/**/src/main/assets
android/**/gradle*
@ -102,3 +104,6 @@ tools/unity-avatar-exporter/Logs
tools/unity-avatar-exporter/Packages
tools/unity-avatar-exporter/ProjectSettings
tools/unity-avatar-exporter/Temp
server-console/package-lock.json
vcpkg/
/tools/nitpick/compiledResources

View file

@ -12,7 +12,7 @@ target_python()
if (HIFI_ANDROID )
execute_process(
COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --android --build-root ${CMAKE_BINARY_DIR}
COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --android ${HIFI_ANDROID_APP} --build-root ${CMAKE_BINARY_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
else()
@ -80,8 +80,28 @@ endif()
if (ANDROID)
set(GLES_OPTION ON)
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
add_definitions(-DHIFI_ANDROID_APP=\"${HIFI_ANDROID_APP}\")
if (
(${HIFI_ANDROID_APP} STREQUAL "questInterface") OR
(${HIFI_ANDROID_APP} STREQUAL "questFramePlayer") OR
(${HIFI_ANDROID_APP} STREQUAL "framePlayer")
)
# We know the quest hardware has this extension, so we can force the use of instanced stereo
add_definitions(-DHAVE_EXT_clip_cull_distance)
# We can also use multiview stereo techniques
add_definitions(-DHAVE_OVR_multiview2)
add_definitions(-DHAVE_OVR_multiview)
# We can also use our own foveated textures
add_definitions(-DHAVE_QCOM_texture_foveated)
# if set, the application itself or some library it depends on MUST implement
# `DisplayPluginList getDisplayPlugins()` and `InputPluginList getInputPlugins()`
add_definitions(-DCUSTOM_INPUT_PLUGINS)
add_definitions(-DCUSTOM_DISPLAY_PLUGINS)
set(PLATFORM_PLUGIN_LIBRARIES oculusMobile oculusMobilePlugin)
endif()
else ()
set(PLATFORM_QT_COMPONENTS WebEngine)
set(PLATFORM_QT_COMPONENTS WebEngine Xml)
endif ()
if (USE_GLES AND (NOT ANDROID))
@ -108,8 +128,10 @@ set(PLATFORM_QT_GL OpenGL)
if (USE_GLES)
add_definitions(-DUSE_GLES)
add_definitions(-DGPU_POINTER_STORAGE_SHARED)
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gles)
else()
add_definitions(-DGPU_POINTER_STORAGE_RAW)
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gl)
endif()
@ -174,7 +196,7 @@ set_packaging_parameters()
# FIXME hack to work on the proper Android toolchain
if (ANDROID)
add_subdirectory(android/app)
add_subdirectory(android/apps/${HIFI_ANDROID_APP})
return()
endif()

View file

@ -4,7 +4,8 @@ During generation, CMake should produce an `install` target and a `package` targ
### Install
The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
The `install` target will copy the High Fidelity targets and their dependencies to your `CMAKE_INSTALL_PREFIX`.
This variable is set by the `project(hifi)` command in `CMakeLists.txt` to `C:/Program Files/hifi` and stored in `build/CMakeCache.txt`
### Packaging
@ -14,17 +15,67 @@ To produce an installer, run the `package` target.
To produce an executable installer on Windows, the following are required:
- [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3
- [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6
- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
- [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7
- [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in)
- [ApplicationID plug-in for Nullsoft](http://nsis.sourceforge.net/ApplicationID_plug-in) - 1.0
1. [7-zip](<https://www.7-zip.org/download.html>)
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.
1. [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.04
Install using defaults (will install to `C:\Program Files (x86)\NSIS`)
1. [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
1. Extract Zip
1. Copy `UAC.nsh` to `C:\Program Files (x86)\NSIS\Include\`
1. Copy `Plugins\x86-ansi\UAC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `Plugins\x86-unicode\UAC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6 (use the link marked **nsProcess_1_6.7z**)
1. Extract Zip
1. Copy `Include\nsProcess.nsh` to `C:\Program Files (x86)\NSIS\Include\`
1. Copy `Plugins\nsProcess.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `Plugins\nsProcessW.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [InetC Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
1. Extract Zip
1. Copy `Plugin\x86-ansi\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `Plugin\x86-unicode\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
1. Extract Zip
1. Copy `NSISpre.nsh` to `C:\Program Files (x86)\NSIS\Include\`
1. Copy `NSISpre.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. [nsisSlideshow Plug-in for Nullsoft](<http://wiz0u.free.fr/prog/nsisSlideshow/>) - 1.7
1. Extract Zip
1. Copy `bin\nsisSlideshow.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `bin\nsisSlideshowW.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in)
1. Download both Zips and unzip
1. Copy `nsisunz\Release\nsisunz.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `NSISunzU\Plugin unicode\nsisunz.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [ApplicationID plug-in for Nullsoft]() - 1.0
1. Download [`Pre-built DLLs`](<https://github.com/connectiblutz/NSIS-ApplicationID/releases/download/1.1/NSIS-ApplicationID.zip>)
1. Extract Zip
1. Copy `Release\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\`
1. Copy `ReleaseUnicode\ApplicationID.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\`
1. [npm](<https://www.npmjs.com/get-npm>)
1. Install version 10.15.0 LTS
1. Perform a clean cmake from a new terminal.
1. Open the `hifi.sln` Solution and select the Release configuration.
1. Build the Solution.
1. Build `packaged-server-console` (found under **Server Console**)
This will add 2 folders to `build\server-console\` -
`server-console-win32-x64` and `x64`
1. Build CMakeTargets->PACKAGE
Installer is now available in `build\_CPack_Packages\win64\NSIS`
#### OS X
Run the `package` target to create an Apple Disk Image (.dmg).
1. [npm](<https://www.npmjs.com/get-npm>)
Install version 10.15.0 LTS
1. Perform a clean cmake.
1. Perform a Release build of ALL_BUILD
1. Perform a Release build of `packaged-server-console`
This will add a folder to `build\server-console\` -
Sandbox-darwin-x64
1. Perform a Release build of `package`
Installer is now available in `build/_CPack_Packages/Darwin/DragNDrop

View file

@ -1,42 +0,0 @@
//
// InterfaceActivity.java
// gvr-interface/java
//
// Created by Stephen Birarda on 1/26/15.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
package io.highfidelity.gvrinterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.util.Log;
import org.qtproject.qt5.android.bindings.QtActivity;
public class InterfaceActivity extends QtActivity {
public static native void handleHifiURL(String hifiURLString);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// Get the intent that started this activity in case we have a hifi:// URL to parse
Intent intent = getIntent();
if (intent.getAction() == Intent.ACTION_VIEW) {
Uri data = intent.getData();
if (data.getScheme().equals("hifi")) {
handleHifiURL(data.toString());
}
}
}
}

View file

@ -0,0 +1,5 @@
set(TARGET_NAME framePlayer)
setup_hifi_library(AndroidExtras)
link_hifi_libraries(shared ktx shaders qml gpu gl ${PLATFORM_GL_BACKEND})
target_link_libraries(${TARGET_NAME} android log m)
target_opengl()

View file

@ -0,0 +1,41 @@
import com.android.builder.core.BuilderConstants
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "io.highfidelity.frameplayer"
minSdkVersion 25
targetSdkVersion 28
ndk { abiFilters 'arm64-v8a' }
externalNativeBuild {
cmake {
arguments '-DHIFI_ANDROID=1',
'-DHIFI_ANDROID_APP=framePlayer',
'-DANDROID_TOOLCHAIN=clang',
'-DANDROID_STL=c++_shared',
'-DCMAKE_VERBOSE_MAKEFILE=ON'
targets = ['framePlayer']
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild.cmake.path '../../../CMakeLists.txt'
variantFilter { variant ->
def build = variant.buildType.name
if (build == BuilderConstants.RELEASE) {
variant.setIgnore(true)
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs')
implementation project(':qt')
}

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.highfidelity.frameplayer"
android:versionCode="1"
android:versionName="1.0"
android:installLocation="auto">
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application android:label="Frame Viewer"
android:allowBackup="false"
android:name="org.qtproject.qt5.android.bindings.QtApplication"
tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon">
<meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
<activity
android:name="org.qtproject.qt5.android.bindings.QtActivity"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
android:launchMode="singleTask"
android:screenOrientation="landscape"
android:excludeFromRecents="false"
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode">
<!-- JNI nonsense -->
<meta-data android:name="android.app.lib_name" android:value="framePlayer"/>
<!-- Qt nonsense -->
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so"/>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,6 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/">
<file>qml/main.qml</file>
</qresource>
</RCC>

View file

@ -0,0 +1,91 @@
//
// Created by Bradley Austin Davis on 2018/10/21
// Copyright 2014 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 "PlayerWindow.h"
#include <QtCore/QFileInfo>
#include <QtGui/QImageReader>
#include <QtQml/QQmlContext>
#include <QtQuick/QQuickItem>
#include <gpu/Frame.h>
#include <gpu/FrameIO.h>
PlayerWindow::PlayerWindow() {
setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint);
setSurfaceType(QSurface::OpenGLSurface);
create();
showFullScreen();
// Make sure the window has been created by processing events
QCoreApplication::processEvents();
// Start the rendering thread
_renderThread.initialize(this, &_surface);
// Start the UI
_surface.resize(size());
connect(&_surface, &hifi::qml::OffscreenSurface::rootContextCreated, this, [](QQmlContext* context){
context->setContextProperty("FRAMES_FOLDER", "file:assets:/frames");
});
_surface.load("qrc:///qml/main.qml");
// Connect the UI handler
QObject::connect(_surface.getRootItem(), SIGNAL(loadFile(QString)),
this, SLOT(loadFile(QString))
);
// Turn on UI input events
installEventFilter(&_surface);
}
PlayerWindow::~PlayerWindow() {
}
// static const char* FRAME_FILE = "assets:/frames/20190110_1635.json";
static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) {
QImage image;
QImageReader(filename.c_str()).read(&image);
if (layer > 0) {
return;
}
texture->assignStoredMip(0, image.byteCount(), image.constBits());
}
void PlayerWindow::loadFile(QString filename) {
QString realFilename = QUrl(filename).toLocalFile();
if (QFileInfo(realFilename).exists()) {
auto frame = gpu::readFrame(realFilename.toStdString(), _renderThread._externalTexture, &textureLoader);
_surface.pause();
_renderThread.submitFrame(frame);
}
}
void PlayerWindow::touchEvent(QTouchEvent* event) {
// Super basic input handling when the 3D scene is active.... tap with two finders to return to the
// QML UI
static size_t touches = 0;
switch (event->type()) {
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
touches = std::max<size_t>(touches, event->touchPoints().size());
break;
case QEvent::TouchEnd:
if (touches >= 2) {
_renderThread.submitFrame(nullptr);
_surface.resume();
}
touches = 0;
break;
default:
break;
}
}

View file

@ -0,0 +1,35 @@
//
// Created by Bradley Austin Davis on 2018/10/21
// Copyright 2014 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
//
#pragma once
#include <QtGui/QWindow>
#include <QtCore/QSettings>
#include <qml/OffscreenSurface.h>
#include <gpu/Forward.h>
#include "RenderThread.h"
// Create a simple OpenGL window that renders text in various ways
class PlayerWindow : public QWindow {
Q_OBJECT
public:
PlayerWindow();
virtual ~PlayerWindow();
protected:
void touchEvent(QTouchEvent *ev) override;
public slots:
void loadFile(QString filename);
private:
hifi::qml::OffscreenSurface _surface;
QSettings _settings;
RenderThread _renderThread;
};

View file

@ -0,0 +1,162 @@
//
// Created by Bradley Austin Davis on 2018/10/21
// Copyright 2014 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 "RenderThread.h"
#include <QtGui/QWindow>
void RenderThread::submitFrame(const gpu::FramePointer& frame) {
std::unique_lock<std::mutex> lock(_frameLock);
_pendingFrames.push(frame);
}
void RenderThread::move(const glm::vec3& v) {
std::unique_lock<std::mutex> lock(_frameLock);
_correction = glm::inverse(glm::translate(mat4(), v)) * _correction;
}
void RenderThread::setup() {
// Wait until the context has been moved to this thread
{ std::unique_lock<std::mutex> lock(_frameLock); }
makeCurrent();
// Disable vsync for profiling
::gl::setSwapInterval(0);
glClearColor(1, 1, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
_glContext.swapBuffers();
// GPU library init
gpu::Context::init<gpu::gl::GLBackend>();
_gpuContext = std::make_shared<gpu::Context>();
_backend = _gpuContext->getBackend();
_gpuContext->beginFrame();
_gpuContext->endFrame();
makeCurrent();
glGenFramebuffers(1, &_uiFbo);
glGenTextures(1, &_externalTexture);
glBindTexture(GL_TEXTURE_2D, _externalTexture);
static const glm::u8vec4 color{ 0 };
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color);
glClearColor(0, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
_glContext.swapBuffers();
}
void RenderThread::initialize(QWindow* window, hifi::qml::OffscreenSurface* offscreen) {
std::unique_lock<std::mutex> lock(_frameLock);
setObjectName("RenderThread");
Parent::initialize();
_offscreen = offscreen;
_window = window;
_glContext.setWindow(_window);
_glContext.create();
_glContext.makeCurrent();
hifi::qml::OffscreenSurface::setSharedContext(_glContext.qglContext());
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
_glContext.swapBuffers();
_glContext.doneCurrent();
_glContext.moveToThread(_thread);
_thread->setObjectName("RenderThread");
}
void RenderThread::shutdown() {
_activeFrame.reset();
while (!_pendingFrames.empty()) {
_gpuContext->consumeFrameUpdates(_pendingFrames.front());
_pendingFrames.pop();
}
_gpuContext->shutdown();
_gpuContext.reset();
}
void RenderThread::renderFrame() {
auto windowSize = _window->geometry().size();
uvec2 readFboSize;
uint32_t readFbo{ 0 };
if (_activeFrame) {
const auto &frame = _activeFrame;
_backend->recycle();
_backend->syncCache();
_gpuContext->enableStereo(frame->stereoState._enable);
if (frame && !frame->batches.empty()) {
_gpuContext->executeFrame(frame);
}
auto &glBackend = static_cast<gpu::gl::GLBackend&>(*_backend);
readFbo = glBackend.getFramebufferID(frame->framebuffer);
readFboSize = frame->framebuffer->getSize();
CHECK_GL_ERROR();
} else {
hifi::qml::OffscreenSurface::TextureAndFence newTextureAndFence;
if (_offscreen->fetchTexture(newTextureAndFence)) {
if (_uiTexture != 0) {
auto readFence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glFlush();
_offscreen->getDiscardLambda()(_uiTexture, readFence);
_uiTexture = 0;
}
glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
glDeleteSync((GLsync)newTextureAndFence.second);
_uiTexture = newTextureAndFence.first;
glBindFramebuffer(GL_READ_FRAMEBUFFER, _uiFbo);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _uiTexture, 0);
}
if (_uiTexture != 0) {
readFbo = _uiFbo;
readFboSize = { windowSize.width(), windowSize.height() };
}
}
if (readFbo) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
glBlitFramebuffer(
0, 0, readFboSize.x, readFboSize.y,
0, 0, windowSize.width(), windowSize.height(),
GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
} else {
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
_glContext.swapBuffers();
}
void RenderThread::updateFrame() {
std::queue<gpu::FramePointer> pendingFrames;
{
std::unique_lock<std::mutex> lock(_frameLock);
pendingFrames.swap(_pendingFrames);
}
while (!pendingFrames.empty()) {
_activeFrame = pendingFrames.front();
pendingFrames.pop();
if (_activeFrame) {
_gpuContext->consumeFrameUpdates(_activeFrame);
}
}
}
bool RenderThread::process() {
updateFrame();
makeCurrent();
renderFrame();
return true;
}

View file

@ -0,0 +1,54 @@
//
// Created by Bradley Austin Davis on 2018/10/21
// Copyright 2014 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
//
#pragma once
#include <GenericThread.h>
#include <gl/Context.h>
#include <gpu/gl/GLBackend.h>
#include <qml/OffscreenSurface.h>
class RenderThread : public GenericThread {
using Parent = GenericThread;
public:
QWindow* _window{ nullptr };
std::mutex _mutex;
gpu::ContextPointer _gpuContext; // initialized during window creation
std::shared_ptr<gpu::Backend> _backend;
std::atomic<size_t> _presentCount{ 0 };
std::mutex _frameLock;
std::queue<gpu::FramePointer> _pendingFrames;
gpu::FramePointer _activeFrame;
uint32_t _externalTexture{ 0 };
glm::mat4 _correction;
hifi::qml::OffscreenSurface* _offscreen{ nullptr };
gl::Context _glContext;
uint32_t _uiTexture{ 0 };
uint32_t _uiFbo{ 0 };
void move(const glm::vec3& v);
void setup() override;
bool process() override;
void shutdown() override;
void initialize(QWindow* window, hifi::qml::OffscreenSurface* offscreen);
void submitFrame(const gpu::FramePointer& frame);
void updateFrame();
void renderFrame();
bool makeCurrent() {
return _glContext.makeCurrent();
}
void doneCurrent() {
_glContext.doneCurrent();
}
};

View file

@ -0,0 +1,54 @@
//
// Created by Bradley Austin Davis on 2018/11/22
// Copyright 2014 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 <android/log.h>
#include <QtGui/QGuiApplication>
#include <QtCore/QTimer>
#include <QtCore/QFileInfo>
#include <Trace.h>
#include "PlayerWindow.h"
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (!message.isEmpty()) {
const char * local=message.toStdString().c_str();
switch (type) {
case QtDebugMsg:
__android_log_write(ANDROID_LOG_DEBUG,"Interface",local);
break;
case QtInfoMsg:
__android_log_write(ANDROID_LOG_INFO,"Interface",local);
break;
case QtWarningMsg:
__android_log_write(ANDROID_LOG_WARN,"Interface",local);
break;
case QtCriticalMsg:
__android_log_write(ANDROID_LOG_ERROR,"Interface",local);
break;
case QtFatalMsg:
default:
__android_log_write(ANDROID_LOG_FATAL,"Interface",local);
abort();
}
}
}
int main(int argc, char** argv) {
setupHifiApplication("gpuFramePlayer");
QGuiApplication app(argc, argv);
auto oldMessageHandler = qInstallMessageHandler(messageHandler);
DependencyManager::set<tracing::Tracer>();
PlayerWindow window;
app.exec();
qInstallMessageHandler(oldMessageHandler);
return 0;
}

View file

@ -0,0 +1,36 @@
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import Qt.labs.folderlistmodel 2.11
Item {
id: root
width: 640
height: 480
ListView {
anchors.fill: parent
FolderListModel {
id: folderModel
folder: FRAMES_FOLDER
nameFilters: ["*.json"]
}
Component {
id: fileDelegate
Text {
text: fileName
font.pointSize: 36
MouseArea {
anchors.fill: parent
onClicked: root.loadFile(folderModel.folder + "/" + fileName);
}
}
}
model: folderModel
delegate: fileDelegate
}
signal loadFile(string filename);
}

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name" translatable="false">GPU Frame Player</string>
</resources>

View file

@ -4,10 +4,10 @@ link_hifi_libraries(shared task networking gl gpu qml image fbx hfm render-utils
target_opengl()
target_bullet()
set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../interface")
set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../interface")
add_subdirectory("${INTERFACE_DIR}" "libraries/interface")
include_directories("${INTERFACE_DIR}/src")
set(HIFI_CODEC_PLUGIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../plugins/hifiCodec")
set(HIFI_CODEC_PLUGIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/hifiCodec")
add_subdirectory("${HIFI_CODEC_PLUGIN_DIR}" "libraries/hifiCodecPlugin")
target_link_libraries(native-lib android log m interface)
@ -15,16 +15,3 @@ target_link_libraries(native-lib android log m interface)
set(GVR_ROOT "${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/")
target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers" "libraries/ui/src")
target_link_libraries(native-lib "${GVR_ROOT}/libraries/libgvr.so" ui)
# finished libraries
# core -> qt
# networking -> openssl, tbb
# fbx -> draco
# physics -> bullet
# entities-renderer -> polyvox
# unfinished libraries
# image -> nvtt (doesn't look good, but can be made optional)
# script-engine -> quazip (probably not required for the android client)

View file

@ -1,5 +1,37 @@
import org.apache.tools.ant.taskdefs.condition.Os
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
allprojects {
repositories {
jcenter()
google()
}
}
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) }
}
}
apply plugin: 'com.android.application'
android {
@ -19,17 +51,17 @@ android {
externalNativeBuild {
cmake {
arguments '-DHIFI_ANDROID=1',
'-DHIFI_ANDROID_APP=interface',
'-DANDROID_PLATFORM=android-24',
'-DANDROID_TOOLCHAIN=clang',
'-DANDROID_STL=c++_shared',
'-DQT_CMAKE_PREFIX_PATH=' + HIFI_ANDROID_PRECOMPILED + '/qt/lib/cmake',
'-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED,
'-DRELEASE_NUMBER=' + RELEASE_NUMBER,
'-DRELEASE_TYPE=' + RELEASE_TYPE,
'-DSTABLE_BUILD=' + STABLE_BUILD,
'-DDISABLE_QML=OFF',
'-DDISABLE_KTX_CACHE=OFF',
'-DUSE_BREAKPAD=' + (System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_TOKEN") ? 'ON' : 'OFF');
targets = ['native-lib']
}
}
signingConfigs {
@ -72,7 +104,7 @@ android {
externalNativeBuild {
cmake {
path '../../CMakeLists.txt'
path '../../../CMakeLists.txt'
}
}
@ -82,6 +114,7 @@ android {
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()
@ -97,7 +130,7 @@ android {
// Copy the compiled resources generated by the external native build
copy {
from new File(projectDir, "../../interface/compiledResources")
from new File(projectDir, "../../../interface/compiledResources")
into outputDir
duplicatesStrategy DuplicatesStrategy.INCLUDE
eachFile { details ->
@ -108,7 +141,7 @@ android {
// Copy the scripts directory
copy {
from new File(projectDir, "../../scripts")
from new File(projectDir, "../../../scripts")
into new File(outputDir, "scripts")
duplicatesStrategy DuplicatesStrategy.INCLUDE
eachFile { details->
@ -123,12 +156,6 @@ android {
assetList.each { file -> out.println(file) }
}
}
variant.outputs.all {
if (RELEASE_NUMBER != '0') {
outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk"
}
}
}
}
@ -157,5 +184,6 @@ dependencies {
api 'com.sothree.slidinguppanel:library:3.4.0'
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs')
implementation project(':qt')
}

View file

@ -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

View file

@ -0,0 +1,44 @@
{
"avatars": [
{
"name": "Wooden Mannequin",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/7fe80a1e-f445-4800-9e89-40e677b03bee/large/hifi-mp-7fe80a1e-f445-4800-9e89-40e677b03bee.jpg",
"url": "qrc:////meshes/defaultAvatar_full.fst"
},
{
"name": "Anime-Styled Boy",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg",
"url": "http://mpassets.highfidelity.com/46e0fd52-3cff-462f-ba97-927338d88295-v1/AnimeBoy2.fst"
},
{
"name": "Anime-Styled Girl",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/1e7e43f6-1757-44d3-baa4-756827d96311/large/hifi-mp-1e7e43f6-1757-44d3-baa4-756827d96311.jpg",
"url": "http://mpassets.highfidelity.com/0dce3426-55c8-4641-8dd5-d76eb575b64a-v1/Anime_F_Outfit.fst"
},
{
"name": "Last Legends: Male Avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/28569047-6f1a-4100-af67-8054ec397cc3/thumbnail/hifi-mp-28569047-6f1a-4100-af67-8054ec397cc3.jpg",
"url": "http://mpassets.highfidelity.com/28569047-6f1a-4100-af67-8054ec397cc3-v1/LLMale2.fst"
},
{
"name": "Last Legends: Female Avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/8d823be5-6197-4418-b984-eb94160ed956/thumbnail/hifi-mp-8d823be5-6197-4418-b984-eb94160ed956.jpg",
"url": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg"
},
{
"name": "Matthew: Photo-real avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/b652081b-a199-425e-ae5c-7815721bdc09/thumbnail/hifi-mp-b652081b-a199-425e-ae5c-7815721bdc09.jpg",
"url": "http://mpassets.highfidelity.com/b652081b-a199-425e-ae5c-7815721bdc09-v1/matthew.fst"
},
{
"name": "Priscilla: Photo real avatar",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339/thumbnail/hifi-mp-e7565f93-8bc5-47c2-b6eb-b3b31d4a1339.jpg",
"url": "http://mpassets.highfidelity.com/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339-v1/priscilla.fst"
},
{
"name": "H1-F1 Optical Interpreter bot",
"preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/469c8b66-e3c2-47fb-9820-e306b1dd15c4/large/hifi-mp-469c8b66-e3c2-47fb-9820-e306b1dd15c4.jpg",
"url": "http://mpassets.highfidelity.com/469c8b66-e3c2-47fb-9820-e306b1dd15c4-v1/optical_interpreter[1].fst"
}
]
}

View file

@ -149,7 +149,7 @@ void unpackAndroidAssets() {
extern "C" {
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) {
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject instance, jobject asset_mgr) {
g_assetManager = AAssetManager_fromJava(env, asset_mgr);
qRegisterMetaType<QAndroidJniObject>("QAndroidJniObject");
__interfaceActivity = QAndroidJniObject(instance);
@ -493,6 +493,34 @@ Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(J
}
JNIEXPORT jstring JNICALL
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_getDisplayName(JNIEnv *env,
jobject instance) {
QString displayName = AndroidHelper::instance().getDisplayName();
return env->NewStringUTF(displayName.toLatin1().data());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setDisplayName(JNIEnv *env,
jobject instance,
jstring name_) {
const char *c_name = env->GetStringUTFChars(name_, 0);
const QString name = QString::fromUtf8(c_name);
env->ReleaseStringUTFChars(name_, c_name);
AndroidHelper::instance().setDisplayName(name);
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setAvatarUrl(JNIEnv *env,
jobject instance,
jstring url_) {
const char *url = env->GetStringUTFChars(url_, 0);
QString avatarUrl = QString::fromUtf8(url);
AndroidHelper::instance().setMyAvatarUrl(avatarUrl);
env->ReleaseStringUTFChars(url_, url);
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_MainActivity_logout(JNIEnv *env, jobject instance) {
DependencyManager::get<AccountManager>()->logout();

View file

@ -24,6 +24,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@ -31,6 +32,7 @@ import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.SlidingDrawer;
import org.qtproject.qt5.android.QtNative;
import org.qtproject.qt5.android.QtLayout;
import org.qtproject.qt5.android.QtSurface;
import org.qtproject.qt5.android.bindings.QtActivity;
@ -53,6 +55,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
public static final String DOMAIN_URL = "url";
public static final String EXTRA_GOTO_USERNAME = "gotousername";
private static final String TAG = "Interface";
public static final String EXTRA_ARGS = "args";
private static final int WEB_DRAWER_RIGHT_MARGIN = 262;
private static final int WEB_DRAWER_BOTTOM_MARGIN = 150;
private static final int NORMAL_DPI = 160;
@ -61,7 +64,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
private HeadsetStateReceiver headsetStateReceiver;
//public static native void handleHifiURL(String hifiURLString);
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
private native void nativeOnCreate(AssetManager assetManager);
private native void nativeOnDestroy();
private native void nativeGotoUrl(String url);
private native void nativeGoToUser(String username);
@ -77,6 +80,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
private boolean nativeEnterBackgroundCallEnqueued = false;
private SlidingDrawer mWebSlidingDrawer;
private boolean mStartInDomain;
// 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.
@ -92,8 +96,14 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
public void onCreate(Bundle savedInstanceState) {
super.isLoading = true;
Intent intent = getIntent();
if (intent.hasExtra(DOMAIN_URL) && !intent.getStringExtra(DOMAIN_URL).isEmpty()) {
if (intent.hasExtra(DOMAIN_URL) && !TextUtils.isEmpty(intent.getStringExtra(DOMAIN_URL))) {
intent.putExtra("applicationArguments", "--url " + intent.getStringExtra(DOMAIN_URL));
} else if (intent.hasExtra(EXTRA_ARGS)) {
String args = intent.getStringExtra(EXTRA_ARGS);
if (!TextUtils.isEmpty(args)) {
mStartInDomain = true;
intent.putExtra("applicationArguments", args);
}
}
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@ -114,7 +124,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
assetManager = getResources().getAssets();
//nativeGvrApi =
nativeOnCreate(this, assetManager /*, gvrApi.getNativeGvrContext()*/);
nativeOnCreate(assetManager /*, gvrApi.getNativeGvrContext()*/);
final View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
@ -124,7 +134,10 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
getActionBar().hide();
}
});
startActivity(new Intent(this, SplashActivity.class));
Intent splashIntent = new Intent(this, SplashActivity.class);
splashIntent.putExtra(SplashActivity.EXTRA_START_IN_DOMAIN, mStartInDomain);
startActivity(splashIntent);
mVibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE);
headsetStateReceiver = new HeadsetStateReceiver();
}
@ -166,8 +179,27 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
@Override
protected void onDestroy() {
super.onDestroy();
nativeOnDestroy();
/*
cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly
After Qt upgrade to 5.11 we had a black screen crash after closing the application with
the hardware button "Back" and trying to start the app again. It could only be fixed after
totally closing the app swiping it in the list of running apps.
This problem did not happen with the previous Qt version.
After analysing changes we came up with this case and change:
https://codereview.qt-project.org/#/c/218882/
In summary they've moved libs loading to the same thread as main() and as a matter of correctness
in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();`
That exit call is the main reason of this problem.
In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the
entire onDestroy method including that thread exit line for other three lines that purposely
terminate qt (borrowed from QtActivityDelegate::onDestroy as well).
*/
QtNative.terminateQt();
QtNative.setActivity(null, null);
System.exit(0);
super.onDestroy();
}
@Override

View file

@ -33,13 +33,15 @@ import com.squareup.picasso.Picasso;
import io.highfidelity.hifiinterface.fragment.FriendsFragment;
import io.highfidelity.hifiinterface.fragment.HomeFragment;
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
import io.highfidelity.hifiinterface.fragment.ProfileFragment;
import io.highfidelity.hifiinterface.fragment.SettingsFragment;
import io.highfidelity.hifiinterface.fragment.SignupFragment;
import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
HomeFragment.OnHomeInteractionListener,
FriendsFragment.OnHomeInteractionListener {
FriendsFragment.OnHomeInteractionListener,
ProfileFragment.OnProfileInteractionListener {
private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
public static final String DEFAULT_FRAGMENT = "Home";
@ -61,6 +63,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
private View mProfilePanel;
private TextView mLogoutOption;
private MenuItem mPeopleMenuItem;
private MenuItem mProfileMenuItem;
private boolean backToScene;
private String backToUrl;
@ -83,6 +86,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people);
mProfileMenuItem = mNavigationView.getMenu().findItem(R.id.action_profile);
updateDebugMenu(mNavigationView.getMenu());
Toolbar toolbar = findViewById(R.id.toolbar);
@ -162,6 +167,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true, true);
}
private void loadProfileFragment() {
Fragment fragment = ProfileFragment.newInstance();
loadFragment(fragment, getString(R.string.profile), getString(R.string.tagFragmentProfile), true, true);
}
private void loadSettingsFragment() {
SettingsFragment fragment = SettingsFragment.newInstance();
@ -261,6 +272,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
case R.id.action_people:
loadPeopleFragment();
return true;
case R.id.action_profile:
loadProfileFragment();
break;
case R.id.action_debug_settings:
loadSettingsFragment();
return true;
@ -351,6 +365,21 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
goToUser(username);
}
@Override
public void onCancelProfileEdit() {
loadHomeFragment(false);
}
@Override
public void onCompleteProfileEdit() {
loadHomeFragment(false);
}
@Override
public void onAvatarChosen() {
loadHomeFragment(false);
}
private class RoundProfilePictureCallback implements Callback {
@Override
public void onSuccess() {

View file

@ -9,6 +9,7 @@ import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.text.TextUtils;
import org.json.JSONException;
import org.json.JSONObject;
@ -27,9 +28,14 @@ public class PermissionChecker extends Activity {
private static final boolean CHOOSE_AVATAR_ON_STARTUP = false;
private static final String TAG = "Interface";
private static final String EXTRA_ARGS = "args";
private String mArgs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mArgs =(getIntent().getStringExtra(EXTRA_ARGS));
Intent myIntent = new Intent(this, BreakpadUploaderService.class);
startService(myIntent);
if (CHOOSE_AVATAR_ON_STARTUP) {
@ -76,6 +82,11 @@ public class PermissionChecker extends Activity {
private void launchActivityWithPermissions(){
Intent i = new Intent(this, InterfaceActivity.class);
if (!TextUtils.isEmpty(mArgs)) {
i.putExtra(EXTRA_ARGS, mArgs);
}
startActivity(i);
finish();
}

View file

@ -7,6 +7,9 @@ import android.view.View;
public class SplashActivity extends Activity {
public static final String EXTRA_START_IN_DOMAIN = "start-in-domain";
private boolean mStartInDomain;
private native void registerLoadCompleteListener();
@Override
@ -36,13 +39,27 @@ public class SplashActivity extends Activity {
}
public void onAppLoadedComplete() {
if (HifiUtils.getInstance().isUserLoggedIn()) {
startActivity(new Intent(this, MainActivity.class));
} else {
Intent menuIntent = new Intent(this, LoginMenuActivity.class);
menuIntent.putExtra(LoginMenuActivity.EXTRA_FINISH_ON_BACK, true);
startActivity(menuIntent);
if (!mStartInDomain) {
if (HifiUtils.getInstance().isUserLoggedIn()) {
startActivity(new Intent(this, MainActivity.class));
} else {
Intent menuIntent = new Intent(this, LoginMenuActivity.class);
menuIntent.putExtra(LoginMenuActivity.EXTRA_FINISH_ON_BACK, true);
startActivity(menuIntent);
}
}
SplashActivity.this.finish();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(EXTRA_START_IN_DOMAIN, mStartInDomain);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mStartInDomain = savedInstanceState.getBoolean(EXTRA_START_IN_DOMAIN, false);
}
}

View file

@ -0,0 +1,126 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.TextView;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.provider.AvatarProvider;
import io.highfidelity.hifiinterface.view.AvatarAdapter;
public class ProfileFragment extends Fragment {
private TextView mDisplayName;
private Button mOkButton;
private OnProfileInteractionListener mListener;
private AvatarProvider mAvatarsProvider;
private native String getDisplayName();
private native void setDisplayName(String name);
private native void setAvatarUrl(String url);
public ProfileFragment() {
// Required empty public constructor
}
public static ProfileFragment newInstance() {
ProfileFragment fragment = new ProfileFragment();
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_profile, container, false);
mDisplayName = rootView.findViewById(R.id.displayName);
mDisplayName.setText(getDisplayName());
mDisplayName.setOnEditorActionListener((textView, actionId, keyEvent) -> onDisplayNameEditorAction(textView, actionId, keyEvent));
mOkButton = rootView.findViewById(R.id.okButton);
mOkButton.setOnClickListener(view -> onOkButtonClicked());
rootView.findViewById(R.id.cancel).setOnClickListener(view -> onCancelProfileEdit());
RecyclerView avatarsView = rootView.findViewById(R.id.gridview);
int numberOfColumns = 1;
mAvatarsProvider = new AvatarProvider(getContext());
GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns);
avatarsView.setLayoutManager(gridLayoutMgr);
AvatarAdapter avatarAdapter = new AvatarAdapter(getContext(), mAvatarsProvider);
avatarsView.setAdapter(avatarAdapter);
avatarAdapter.loadAvatars();
avatarAdapter.setClickListener((view, position, avatar) -> {
setAvatarUrl(avatar.avatarUrl);
if (mListener != null) {
mListener.onAvatarChosen();
}
});
return rootView;
}
private void onOkButtonClicked() {
setDisplayName(mDisplayName.getText().toString());
View view = getActivity().getCurrentFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
if (mListener != null) {
mListener.onCompleteProfileEdit();
}
}
private boolean onDisplayNameEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
mOkButton.performClick();
return true;
}
return false;
}
private void onCancelProfileEdit() {
View view = getActivity().getCurrentFocus();
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
if (mListener != null) {
mListener.onCancelProfileEdit();
}
}
/**
* Processes the back pressed event and returns true if it was managed by this Fragment
* @return
*/
public boolean onBackPressed() {
return false;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnProfileInteractionListener) {
mListener = (OnProfileInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnProfileInteractionListener");
}
}
public interface OnProfileInteractionListener {
void onCancelProfileEdit();
void onCompleteProfileEdit();
void onAvatarChosen();
}
}

View file

@ -0,0 +1,70 @@
package io.highfidelity.hifiinterface.provider;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import io.highfidelity.hifiinterface.view.AvatarAdapter;
/**
* Created by gcalero on 1/21/19
*/
public class AvatarProvider {
private static final String AVATARS_JSON = "avatars.json";
private static final String JSON_FIELD_NAME = "name";
private static final String JSON_FIELD_URL = "url";
private static final String JSON_FIELD_IMAGE = "preview_image";
private static final String JSON_FIELD_AVATARS_ARRAY = "avatars";
private final Context mContext;
public interface AvatarsCallback {
void retrieveOk(List<AvatarAdapter.Avatar> avatars);
void retrieveError(Exception e, String message);
}
public AvatarProvider(Context context) {
mContext = context;
}
public void retrieve(AvatarsCallback avatarsCallback)
{
try {
JSONObject obj = new JSONObject(loadJSONFromAssets());
JSONArray m_jArry = obj.getJSONArray(JSON_FIELD_AVATARS_ARRAY);
ArrayList<AvatarAdapter.Avatar> avatars = new ArrayList<>();
for (int i = 0; i < m_jArry.length(); i++) {
JSONObject jo_inside = m_jArry.getJSONObject(i);
AvatarAdapter.Avatar anAvatar = new AvatarAdapter.Avatar();
anAvatar.avatarName = jo_inside.getString(JSON_FIELD_NAME);
anAvatar.avatarPreviewUrl = jo_inside.getString(JSON_FIELD_IMAGE);
anAvatar.avatarUrl = jo_inside.getString(JSON_FIELD_URL);
avatars.add(anAvatar);
}
avatarsCallback.retrieveOk(avatars);
} catch (IOException e) {
avatarsCallback.retrieveError(e, "Failed retrieving avatar JSON");
} catch (JSONException e) {
avatarsCallback.retrieveError(e, "Failed parsing avatar JSON");
}
}
private String loadJSONFromAssets() throws IOException {
String json = null;
InputStream is = mContext.getAssets().open(AVATARS_JSON);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
return json;
}
}

View file

@ -0,0 +1,111 @@
package io.highfidelity.hifiinterface.view;
import android.content.Context;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.List;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.provider.AvatarProvider;
/**
* Created by gcalero on 1/21/19
*/
public class AvatarAdapter extends RecyclerView.Adapter<AvatarAdapter.ViewHolder> {
private static final String TAG = "Interface";
private final Context mContext;
private final LayoutInflater mInflater;
private final AvatarProvider mProvider;
private List<Avatar> mAvatars = new ArrayList<>();
private ItemClickListener mClickListener;
public AvatarAdapter(Context context, AvatarProvider provider) {
mContext = context;
mInflater = LayoutInflater.from(mContext);
mProvider = provider;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.avatar_item, parent, false);
return new AvatarAdapter.ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
AvatarAdapter.Avatar anAvatar = mAvatars.get(position);
assert(holder.mName != null);
holder.mName.setText(anAvatar.avatarName);
Uri uri = Uri.parse(anAvatar.avatarPreviewUrl);
Picasso.get().load(uri).into(holder.mPreviewImage);
}
@Override
public int getItemCount() {
return mAvatars.size();
}
public void loadAvatars() {
mProvider.retrieve(new AvatarProvider.AvatarsCallback() {
@Override
public void retrieveOk(List<AvatarAdapter.Avatar> avatars) {
mAvatars = new ArrayList<>(avatars);
notifyDataSetChanged();
}
@Override
public void retrieveError(Exception e, String message) {
Log.e(TAG, message, e);
}
});
}
public void setClickListener(ItemClickListener clickListener) {
mClickListener = clickListener;
}
public interface ItemClickListener {
void onItemClick(View view, int position, Avatar avatar);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView mName;
ImageView mPreviewImage;
public ViewHolder(View itemView) {
super(itemView);
mName = itemView.findViewById(R.id.avatarName);
assert (mName != null);
mPreviewImage = itemView.findViewById(R.id.avatarPreview);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
int position= getAdapterPosition();
if (mClickListener != null) {
mClickListener.onItemClick(view, position, mAvatars.get(position));
}
}
}
public static class Avatar {
public String avatarName;
public String avatarUrl;
public String avatarPreviewUrl;
public Avatar() { }
}
}

View file

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--suppress AndroidUnknownAttribute -->
<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="192"
android:viewportHeight="192"
android:width="192dp"
android:height="192dp">
<path
android:pathData="M189.5 96.5A93.5 93.5 0 0 1 96 190 93.5 93.5 0 0 1 2.5 96.5 93.5 93.5 0 0 1 96 3 93.5 93.5 0 0 1 189.5 96.5Z"
android:fillColor="#333333" />
<path
android:pathData="M96.2 173.1c-10.3 0 -20.4 -2.1 -29.8 -6 -9.2 -3.8 -17.3 -9.4 -24.3 -16.4 -7 -7 -12.6 -15.2 -16.4 -24.3 -4.1 -9.6 -6.2 -19.6 -6.2 -30 0 -10.3 2.1 -20.4 6 -29.8 3.8 -9.2 9.4 -17.3 16.4 -24.3 7 -7 15.2 -12.6 24.3 -16.4 9.5 -4 19.5 -6 29.8 -6 10.3 0 20.4 2.1 29.8 6 9.2 3.8 17.3 9.4 24.3 16.4 7 7 12.6 15.2 16.4 24.3 4 9.5 6 19.5 6 29.8 0 10.3 -2.1 20.4 -6 29.8 -3.8 9.2 -9.4 17.3 -16.4 24.3 -7 7 -15.2 12.6 -24.3 16.4 -9.2 4.1 -19.3 6.2 -29.6 6.2zm0 -145.3c-37.8 0 -68.6 30.8 -68.6 68.6 0 37.8 30.8 68.6 68.6 68.6 37.8 0 68.6 -30.8 68.6 -68.6 0 -37.8 -30.8 -68.6 -68.6 -68.6z"
android:fillColor="#00b4f0" />
<path
android:pathData="M119.6 129l0 -53.8c3.4 -1.1 5.8 -4.3 5.8 -8 0 -4.6 -3.8 -8.4 -8.4 -8.4 -4.6 0 -8.4 3.8 -8.4 8.4 0 3.6 2.2 6.6 5.4 7.9l0 25L79 83.8 79 64c3.4 -1.1 5.8 -4.3 5.8 -8 0 -4.6 -3.8 -8.4 -8.4 -8.4 -4.6 0 -8.4 3.8 -8.4 8.4 0 3.6 2.2 6.6 5.4 7.9l0 54.1c-3.1 1.2 -5.4 4.3 -5.4 7.9 0 4.6 3.8 8.4 8.4 8.4 4.6 0 8.4 -3.8 8.4 -8.4 0 -3.7 -2.4 -6.9 -5.8 -8l0 -27.3 35 16.3 0 22.2c-3.1 1.2 -5.4 4.3 -5.4 7.9 0 4.6 3.8 8.4 8.4 8.4 4.6 0 8.4 -3.8 8.4 -8.4 0 -3.8 -2.4 -6.9 -5.8 -8z"
android:fillColor="#00b4f0" />
</vector>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<TextView
android:id="@+id/avatarName"
android:fontFamily="@font/raleway_bold"
android:textSize="20sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/colorButton1" />
<ImageView
android:id="@+id/avatarPreview"
android:background="@color/backgroundDark"
android:layout_width="match_parent"
android:layout_height="190dp"
android:scaleType="fitCenter"
app:layout_constraintTop_toBottomOf="@id/avatarName"/>
</android.support.constraint.ConstraintLayout>

Some files were not shown because too many files have changed in this diff Show more