Make Interface run in Android

This commit is contained in:
Gabriel Calero 2017-12-12 15:34:02 -03:00
parent 641c4e537a
commit 76b38bebad
31 changed files with 831 additions and 101 deletions

View file

@ -1,26 +1,28 @@
set(TARGET_NAME native-lib)
setup_hifi_library(Gui Qml Quick)
# Minimal dependencies for testing UI compositing
#link_hifi_libraries(shared networking gl ui)
link_hifi_libraries(
shared networking octree
script-engine recording trackers
gl ktx image gpu gpu-gles render render-utils
physics
audio audio-client
ui midi controllers pointers
model model-networking fbx animation
entities entities-renderer
avatars avatars-renderer
ui-plugins input-plugins
# display-plugins
# auto-updater
)
target_link_libraries(native-lib android log m)
setup_hifi_library()
link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics entities octree)
target_opengl()
target_bullet()
set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../interface")
add_subdirectory("${INTERFACE_DIR}" "libraries/interface")
include_directories("${INTERFACE_DIR}/src")
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")
target_link_libraries(native-lib "${GVR_ROOT}/libraries/libgvr.so")
# 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,10 +1,13 @@
apply plugin: 'com.android.application'
ext.RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
ext.RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
ext.BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : ''
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.highfidelity.iface"
applicationId "io.highfidelity.hifiinterface"
minSdkVersion 24
targetSdkVersion 26
versionCode 1

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.highfidelity.gvrinterface">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.highfidelity.hifiinterface">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="26" />
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
<uses-permission android:name="android.permission.VIBRATE"/>
@ -19,18 +20,34 @@
android:icon="@mipmap/ic_launcher"
android:launchMode="singleTop"
android:roundIcon="@mipmap/ic_launcher_round">
<activity android:name="io.highfidelity.hifiinterface.PermissionChecker">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="io.highfidelity.hifiinterface.WebViewActivity"
android:theme="@android:style/Theme.Material.Light.NoActionBar"/>
<!-- We don't want to show this on Daydream yet (we need to fix the turn-around problem on this screen)
<activity android:name="io.highfidelity.hifiinterface.GvrLoaderActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.intent.category.DAYDREAM"/>
</intent-filter>
</activity>
-->
<activity
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation"
android:name=".InterfaceActivity"
android:label="@string/app_name"
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
android:launchMode="singleTop"
android:screenOrientation="landscape"
android:resizeableActivity="false">
android:launchMode="singleTop"
android:enableVrMode="com.google.vr.vrcore/com.google.vr.vrcore.common.VrCoreListenerService"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="com.google.intent.category.DAYDREAM"/>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="native-lib"/>
@ -43,4 +60,10 @@
<meta-data android:name="android.app.extract_android_style" android:value="full"/>
</activity>
</application>
</manifest>
<!-- Tell the system this app requires OpenGL ES 3.0. -->
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
<uses-feature android:name="android.software.vr.mode" android:required="true"/>
<uses-feature android:name="android.hardware.vr.high_performance" android:required="true"/>
</manifest>

View file

@ -1,4 +1,6 @@
#include <jni.h>
/*
The main function in this file overrides the one in interface library
#include <jni.h>
#include <android/log.h>
#include <QtCore/QDebug>
@ -151,3 +153,4 @@ int main(int argc, char* argv[])
timer.start();
return app.exec();
}
*/

View file

@ -0,0 +1,178 @@
//
// InterfaceActivity.java
// android/app/src/main/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.hifiinterface;
import android.content.Intent;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.WindowManager;
import android.util.Log;
import org.qtproject.qt5.android.bindings.QtActivity;
/*import com.google.vr.cardboard.DisplaySynchronizer;
import com.google.vr.cardboard.DisplayUtils;
import com.google.vr.ndk.base.GvrApi;*/
import android.graphics.Point;
import android.content.res.Configuration;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.view.View;
public class InterfaceActivity extends QtActivity {
//public static native void handleHifiURL(String hifiURLString);
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
//private native void nativeOnPause();
//private native void nativeOnResume();
//private native void nativeOnStop();
//private native void nativeOnStart();
//private native void saveRealScreenSize(int width, int height);
//private native void setAppVersion(String version);
private native long nativeOnExitVr();
private AssetManager assetManager;
private static boolean inVrMode;
// 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.
//private long nativeGvrApi;
public void enterVr() {
//Log.d("[VR]", "Entering Vr mode (java)");
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
inVrMode = true;
}
@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());
// }
}
/* DisplaySynchronizer displaySynchronizer = new DisplaySynchronizer(this, DisplayUtils.getDefaultDisplay(this));
gvrApi = new GvrApi(this, displaySynchronizer);
*/
// Log.d("GVR", "gvrApi.toString(): " + gvrApi.toString());
assetManager = getResources().getAssets();
//nativeGvrApi =
nativeOnCreate(this, assetManager /*, gvrApi.getNativeGvrContext()*/);
Point size = new Point();
getWindowManager().getDefaultDisplay().getRealSize(size);
// saveRealScreenSize(size.x, size.y);
try {
PackageInfo pInfo = this.getPackageManager().getPackageInfo(getPackageName(), 0);
String version = pInfo.versionName;
// setAppVersion(version);
} catch (PackageManager.NameNotFoundException e) {
Log.e("GVR", "Error getting application version", e);
}
final View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
// This is a workaround to hide the menu bar when the virtual keyboard is shown from Qt
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int heightDiff = rootView.getRootView().getHeight() - rootView.getHeight();
if (getActionBar().isShowing()) {
getActionBar().hide();
}
}
});
}
@Override
protected void onPause() {
super.onPause();
//nativeOnPause();
//gvrApi.pauseTracking();
}
@Override
protected void onStart() {
super.onStart();
// nativeOnStart();
}
@Override
protected void onStop() {
Log.d("[Background]","Calling nativeOnStop from InterfaceActivity");
// nativeOnStop();
super.onStop();
}
@Override
protected void onResume() {
super.onResume();
//nativeOnResume();
//gvrApi.resumeTracking();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
// Log.d("[VR]", "Portrait, forcing landscape");
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
if (inVrMode) {
inVrMode = false;
// Log.d("[VR]", "Starting VR exit");
nativeOnExitVr();
} else {
Log.w("[VR]", "Portrait detected but not in VR mode. Should not happen");
}
}
}
public void openUrlInAndroidWebView(String urlString) {
Log.d("openUrl", "Received in open " + urlString);
Intent openUrlIntent = new Intent(this, WebViewActivity.class);
openUrlIntent.putExtra(WebViewActivity.WEB_VIEW_ACTIVITY_EXTRA_URL, urlString);
startActivity(openUrlIntent);
}
/**
* Called when view focus is changed
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
}
}
}

View file

@ -0,0 +1,130 @@
package io.highfidelity.hifiinterface;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.DialogInterface;
import android.app.AlertDialog;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.Log;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
public class PermissionChecker extends Activity {
private static final int REQUEST_PERMISSIONS = 20;
private static final boolean CHOOSE_AVATAR_ON_STARTUP = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (CHOOSE_AVATAR_ON_STARTUP) {
showMenu();
}
this.requestAppPermissions(new
String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA}
,2,REQUEST_PERMISSIONS);
}
public void requestAppPermissions(final String[] requestedPermissions,
final int stringId, 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(){
finish();
Intent i = new Intent(this, InterfaceActivity.class);
startActivity(i);
finish();
}
private void showMenu(){
final List<String> avatarOptions = Arrays.asList("\uD83D\uDC66\uD83C\uDFFB Cody","\uD83D\uDC66\uD83C\uDFFF Will","\uD83D\uDC68\uD83C\uDFFD Albert", "\uD83D\uDC7D Being of Light");
final String[] avatarPaths = {
"http://mpassets.highfidelity.com/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53-v1/cody.fst",
"http://mpassets.highfidelity.com/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73-v1/4618d52e711fbb34df442b414da767bb.fst",
"http://mpassets.highfidelity.com/1e57c395-612e-4acd-9561-e79dbda0bc49-v1/albert.fst" };
final String pathForJson = "/data/data/io.highfidelity.hifiinterface/files/.config/High Fidelity - dev/";
new AlertDialog.Builder(this)
.setTitle("Pick an avatar")
.setItems(avatarOptions.toArray(new CharSequence[avatarOptions.size()]),new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
if(which < avatarPaths.length ) {
JSONObject obj = new JSONObject();
try {
obj.put("firstRun",false);
obj.put("Avatar/fullAvatarURL", avatarPaths[which]);
File directory = new File(pathForJson);
if(!directory.exists()) directory.mkdirs();
File file = new File(pathForJson + "Interface.json");
Writer output = new BufferedWriter(new FileWriter(file));
output.write(obj.toString().replace("\\",""));
output.close();
System.out.println("I Could write config file expect to see the selected avatar"+obj.toString().replace("\\",""));
} catch (JSONException e) {
System.out.println("JSONException something weired went wrong");
} catch (IOException e) {
System.out.println("Could not write file :(");
}
} else {
System.out.println("Default avatar selected...");
}
launchActivityWithPermissions();
}
}).show();
}
@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();
}
}
}

View file

@ -0,0 +1,242 @@
//
// WebViewActivity.java
// interface/java
//
// Created by Cristian Duarte and Gabriel Calero on 8/17/17.
// Copyright 2017 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 hifiinterface.highfidelity.io.mybrowserapplication;
package io.highfidelity.hifiinterface;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.Toast;
import android.widget.Toolbar;
import android.os.Looper;
import java.lang.Thread;
import java.lang.Runnable;
import java.net.MalformedURLException;
import java.net.URL;
public class WebViewActivity extends Activity {
public static final String WEB_VIEW_ACTIVITY_EXTRA_URL = "url";
private native void nativeProcessURL(String url);
private WebView myWebView;
private ProgressBar mProgressBar;
private ActionBar mActionBar;
private String mUrl;
enum SafenessLevel {
NOT_ANALYZED_YET(""),
NOT_SECURE(""),
SECURE("\uD83D\uDD12 "),
BAD_SECURE("\uD83D\uDD13 ");
String icon;
SafenessLevel(String icon) {
this.icon = icon;
}
}
private SafenessLevel safenessLevel = SafenessLevel.NOT_ANALYZED_YET;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view);
setActionBar((Toolbar) findViewById(R.id.toolbar_actionbar));
mActionBar = getActionBar();
mActionBar.setDisplayHomeAsUpEnabled(true);
mProgressBar = (ProgressBar) findViewById(R.id.toolbarProgressBar);
mUrl = getIntent().getStringExtra(WEB_VIEW_ACTIVITY_EXTRA_URL);
myWebView = (WebView) findViewById(R.id.web_view);
myWebView.setWebViewClient(new HiFiWebViewClient());
myWebView.setWebChromeClient(new HiFiWebChromeClient());
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setBuiltInZoomControls(true);
webSettings.setDisplayZoomControls(false);
myWebView.loadUrl(mUrl);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Check if the key event was the Back button and if there's history
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
return true;
}
// If it wasn't the Back key or there's no web page history, bubble up to the default
// system behavior (probably exit the activity)
return super.onKeyDown(keyCode, event);
}
private void showSubtitleWithUrl(String url) {
try {
mActionBar.setSubtitle(safenessLevel.icon + new URL(url.toString()).getHost());
} catch (MalformedURLException e) {
Toast.makeText(WebViewActivity.this, "Error loading page: " + "bad url", Toast.LENGTH_LONG).show();
Log.e("openUrl", "bad url");
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.web_view_menu, menu);
return true;
}
private String intentUrlOrWebUrl() {
return myWebView==null || myWebView.getUrl()==null?mUrl:myWebView.getUrl();
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
switch (item.getItemId()) {
// Respond to the action bar's Up/Home/back button
case android.R.id.home:
finish();
break;
case R.id.action_browser:
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(intentUrlOrWebUrl()));
startActivity(i);
return true;
case R.id.action_share:
Intent is = new Intent(Intent.ACTION_SEND);
is.setType("text/plain");
is.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.web_view_action_share_subject));
is.putExtra(Intent.EXTRA_TEXT, intentUrlOrWebUrl());
startActivity(Intent.createChooser(is, getString(R.string.web_view_action_share_chooser)));
return true;
}
return super.onOptionsItemSelected(item);
}
class HiFiWebViewClient extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mProgressBar.setVisibility(View.GONE);
if (safenessLevel!=SafenessLevel.BAD_SECURE) {
if (url.startsWith("https:")) {
safenessLevel=SafenessLevel.SECURE;
} else {
safenessLevel=SafenessLevel.NOT_SECURE;
}
}
showSubtitleWithUrl(url);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
safenessLevel = SafenessLevel.NOT_ANALYZED_YET;
mProgressBar.setVisibility(View.VISIBLE);
mProgressBar.setProgress(0);
showSubtitleWithUrl(url);
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
Toast.makeText(WebViewActivity.this, "Error loading page: " + error.getDescription(), Toast.LENGTH_LONG).show();
if (ERROR_FAILED_SSL_HANDSHAKE == error.getErrorCode()) {
safenessLevel = SafenessLevel.BAD_SECURE;
}
}
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
Toast.makeText(WebViewActivity.this, "Network Error loading page: " + errorResponse.getReasonPhrase(), Toast.LENGTH_LONG).show();
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
Toast.makeText(WebViewActivity.this, "SSL error loading page: " + error.toString(), Toast.LENGTH_LONG).show();
safenessLevel = SafenessLevel.BAD_SECURE;
}
private boolean isFst(WebResourceRequest request) {
return isFst(request.getUrl().toString());
}
private boolean isFst(String url) {
return url.endsWith(".fst");
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
// managing avatar selections
if (isFst(request)) {
final String url = request.getUrl().toString();
new Thread(new Runnable() {
public void run() {
nativeProcessURL(url);
}
}).start(); // Avoid deadlock in Qt dialog
WebViewActivity.this.finish();
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}
@Override
public void onLoadResource(WebView view, String url) {
if (isFst(url)) {
// processed separately
} else {
super.onLoadResource(view, url);
}
}
}
class HiFiWebChromeClient extends WebChromeClient {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
mProgressBar.setProgress(newProgress);
}
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
mActionBar.setTitle(title);
}
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Toolbar
android:id="@+id/toolbar_actionbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:layout_alignParentTop="true"
style="@android:style/Widget.Material.ActionBar"
android:titleTextAppearance="@style/ActionBarTitleStyle"
android:subtitleTextAppearance="@style/ActionBarSubtitleStyle"
android:navigationIcon="@drawable/ic_close_black_24dp"
android:contentInsetStart="0dp"
android:contentInsetStartWithNavigation="0dp"
android:title="">
</Toolbar>
<WebView
android:id="@+id/web_view"
android:layout_below="@id/toolbar_actionbar"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ProgressBar
android:id="@+id/toolbarProgressBar"
android:layout_below="@id/toolbar_actionbar"
style="?android:attr/progressBarStyleHorizontal"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="3dp"
android:indeterminate="false"
android:padding="0dp" />
</RelativeLayout>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_share"
android:title="@string/web_view_action_share"
android:showAsAction="never"/>
<item android:id="@+id/action_browser"
android:title="@string/web_view_action_open_in_browser"
android:showAsAction="never"/>
</menu>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<!-- Hack to have a desired text size for both orientations (default portrait is too big) -->
<!-- Default text size for action bar title.-->
<dimen name="text_size_title_material_toolbar">14dp</dimen>
<!-- Default text size for action bar subtitle.-->
<dimen name="text_size_subtitle_material_toolbar">12dp</dimen>
</resources>

View file

@ -1,3 +1,8 @@
<resources>
<string name="app_name">TestApp</string>
<string name="app_name" translatable="false">Interface</string>
<string name="web_view_action_open_in_browser" translatable="false">Open in browser</string>
<string name="web_view_action_share" translatable="false">Share link</string>
<string name="web_view_action_share_subject" translatable="false">Shared a link</string>
<string name="web_view_action_share_chooser" translatable="false">Share link</string>
</resources>

View file

@ -11,5 +11,15 @@
<!--item name="android:background">@color/white_opaque</item-->
</style>
<!-- Overriding text size so it's not so big in portrait -->
<style name="ActionBarTitleStyle"
parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title">
<item name="android:textSize">@dimen/text_size_title_material_toolbar</item>
</style>
<style name="ActionBarSubtitleStyle"
parent="@android:style/TextAppearance.Material.Widget.ActionBar.Subtitle">
<item name="android:textSize">@dimen/text_size_subtitle_material_toolbar</item>
</style>
</resources>

View file

@ -11,7 +11,10 @@ macro(TARGET_OPENGL)
find_library(OpenGL OpenGL)
target_link_libraries(${TARGET_NAME} ${OpenGL})
elseif(ANDROID)
target_link_libraries(${TARGET_NAME} GLESv3 EGL)
find_library(EGL EGL)
find_library(OpenGLES3 GLESv3)
list(APPEND IGNORE_COPY_LIBS ${OpenGLES3} ${OpenGLES2} ${EGL})
target_link_libraries(${TARGET_NAME} ${OpenGLES3} ${EGL})
else()
find_package(OpenGL REQUIRED)
if (${OPENGL_INCLUDE_DIR})

View file

@ -58,6 +58,14 @@ else ()
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
endif ()
if (ANDROID)
set(PLATFORM_QT_COMPONENTS AndroidExtras)
# set(PLATFORM_QT_LIBRARIES Qt5::AndroidExtras)
else ()
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
set(PLATFORM_QT_LIBRARIES Qt5::WebEngine Qt5::WebEngineWidgets)
endif ()
find_package(
Qt5 COMPONENTS
Gui Multimedia Network OpenGL Qml Quick Script Svg
@ -93,21 +101,7 @@ endif()
# setup the android parameters that will help us produce an APK
if (ANDROID)
set(ANDROID_APK_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk-build")
set(ANDROID_APK_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}")
set(ANDROID_SDK_ROOT $ENV{ANDROID_HOME})
set(ANDROID_APP_DISPLAY_NAME Interface)
set(ANDROID_API_LEVEL 19)
set(ANDROID_APK_PACKAGE io.highfidelity.interface)
set(ANDROID_ACTIVITY_NAME io.highfidelity.interface.InterfaceActivity)
set(ANDROID_APK_VERSION_NAME "0.1")
set(ANDROID_APK_VERSION_CODE 1)
set(ANDROID_APK_FULLSCREEN TRUE)
set(ANDROID_DEPLOY_QT_INSTALL "--install")
set(BUILD_SHARED_LIBS ON)
endif ()
@ -203,6 +197,13 @@ if (WIN32)
set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG "/OPT:NOREF /OPT:NOICF")
endif()
if (NOT ANDROID)
set(NON_ANDROID_LIBRARIES gpu-gl)
else()
set(ANDROID_LIBRARIES gpu-gles)
endif ()
# link required hifi libraries
link_hifi_libraries(
shared octree ktx gpu gl procedural model render
@ -212,6 +213,8 @@ link_hifi_libraries(
render-utils entities-renderer avatars-renderer ui auto-updater midi
controllers plugins image trackers
ui-plugins display-plugins input-plugins
${ANDROID_LIBRARIES}
${NON_ANDROID_LIBRARIES}
${PLATFORM_GL_BACKEND}
)
@ -262,15 +265,21 @@ endforeach()
# include headers for interface and InterfaceConfig.
include_directories("${PROJECT_SOURCE_DIR}/src")
if (ANDROID)
#set(ANDROID_PLATFORM_QT_LIBRARIES Qt5::WebView)
else()
set(NON_ANDROID_PLATFORM_QT_LIBRARIES Qt5::WebEngine)
endif ()
target_link_libraries(
${TARGET_NAME}
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL
Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg
Qt5::WebChannel Qt5::WebEngine
${PLATFORM_QT_LIBRARIES}
Qt5::WebChannel ${NON_ANDROID_PLATFORM_QT_LIBRARIES}
${ANDROID_PLATFORM_QT_LIBRARIES}
)
if (UNIX)
if (UNIX AND NOT ANDROID)
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
# Linux
target_link_libraries(${TARGET_NAME} pthread atomic)
@ -278,7 +287,7 @@ if (UNIX)
# OSX
target_link_libraries(${TARGET_NAME} pthread)
endif ()
endif(UNIX)
endif()
# assume we are using a Qt build without bearer management
add_definitions(-DQT_NO_BEARERMANAGEMENT)
@ -363,20 +372,6 @@ if (WIN32)
package_libraries_for_deployment()
endif()
if (ANDROID)
set(HIFI_URL_INTENT "<intent-filter>\
\n <action android:name='android.intent.action.VIEW' />\
\n <category android:name='android.intent.category.DEFAULT' />\
\n <category android:name='android.intent.category.BROWSABLE' />\
\n <data android:scheme='hifi' />\
\n </intent-filter>"
)
set(ANDROID_EXTRA_ACTIVITY_XML "${HIFI_URL_INTENT}")
qt_create_apk()
endif ()
add_dependency_external_projects(GifCreator)
find_package(GifCreator REQUIRED)
target_include_directories(${TARGET_NAME} PUBLIC ${GIFCREATOR_INCLUDE_DIRS})

View file

@ -210,7 +210,9 @@
#include "commerce/QmlCommerce.h"
#include "webbrowser/WebBrowserSuggestionsEngine.h"
#ifdef ANDROID
#include <QtAndroidExtras/QAndroidJniObject>
#endif
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
// FIXME seems to be broken.
#if defined(Q_OS_WIN)
@ -232,6 +234,30 @@ extern "C" {
}
#endif
#ifdef ANDROID
extern "C" {
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) {
qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) {
qDebug() << "nativeOnPause";
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnResume(JNIEnv* env, jobject obj) {
qDebug() << "nativeOnResume";
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExitVr(JNIEnv* env, jobject obj) {
qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
}
}
#endif
enum ApplicationEvent {
// Execute a lambda function
Lambda = QEvent::User + 1,

View file

@ -37,7 +37,8 @@ AssetMappingsScriptingInterface::AssetMappingsScriptingInterface() {
void AssetMappingsScriptingInterface::setMapping(QString path, QString hash, QJSValue callback) {
auto assetClient = DependencyManager::get<AssetClient>();
auto request = assetClient->createSetMappingRequest(path, hash);
#ifndef ANDROID
// TODO: just to make android compile
connect(request, &SetMappingRequest::finished, this, [this, callback](SetMappingRequest* request) mutable {
if (callback.isCallable()) {
QJSValueList args { request->getErrorString(), request->getPath() };
@ -46,6 +47,7 @@ void AssetMappingsScriptingInterface::setMapping(QString path, QString hash, QJS
request->deleteLater();
});
#endif
request->start();
}
@ -53,7 +55,8 @@ void AssetMappingsScriptingInterface::setMapping(QString path, QString hash, QJS
void AssetMappingsScriptingInterface::getMapping(QString path, QJSValue callback) {
auto assetClient = DependencyManager::get<AssetClient>();
auto request = assetClient->createGetMappingRequest(path);
#ifndef ANDROID
// TODO: just to make android compile
connect(request, &GetMappingRequest::finished, this, [this, callback](GetMappingRequest* request) mutable {
auto hash = request->getHash();
@ -64,6 +67,7 @@ void AssetMappingsScriptingInterface::getMapping(QString path, QJSValue callback
request->deleteLater();
});
#endif
request->start();
}
@ -140,7 +144,8 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping,
void AssetMappingsScriptingInterface::deleteMappings(QStringList paths, QJSValue callback) {
auto assetClient = DependencyManager::get<AssetClient>();
auto request = assetClient->createDeleteMappingsRequest(paths);
#ifndef ANDROID
// TODO: just to make android compile
connect(request, &DeleteMappingsRequest::finished, this, [this, callback](DeleteMappingsRequest* request) mutable {
if (callback.isCallable()) {
QJSValueList args { request->getErrorString() };
@ -149,6 +154,7 @@ void AssetMappingsScriptingInterface::deleteMappings(QStringList paths, QJSValue
request->deleteLater();
});
#endif
request->start();
}
@ -156,7 +162,8 @@ void AssetMappingsScriptingInterface::deleteMappings(QStringList paths, QJSValue
void AssetMappingsScriptingInterface::getAllMappings(QJSValue callback) {
auto assetClient = DependencyManager::get<AssetClient>();
auto request = assetClient->createGetAllMappingsRequest();
#ifndef ANDROID
// TODO: just to make android compile
connect(request, &GetAllMappingsRequest::finished, this, [this, callback](GetAllMappingsRequest* request) mutable {
auto mappings = request->getMappings();
auto map = callback.engine()->newObject();
@ -172,6 +179,7 @@ void AssetMappingsScriptingInterface::getAllMappings(QJSValue callback) {
request->deleteLater();
});
#endif
request->start();
}
@ -179,7 +187,8 @@ void AssetMappingsScriptingInterface::getAllMappings(QJSValue callback) {
void AssetMappingsScriptingInterface::renameMapping(QString oldPath, QString newPath, QJSValue callback) {
auto assetClient = DependencyManager::get<AssetClient>();
auto request = assetClient->createRenameMappingRequest(oldPath, newPath);
#ifndef ANDROID
// TODO: just to make android compile
connect(request, &RenameMappingRequest::finished, this, [this, callback](RenameMappingRequest* request) mutable {
if (callback.isCallable()) {
QJSValueList args{ request->getErrorString() };
@ -188,14 +197,15 @@ void AssetMappingsScriptingInterface::renameMapping(QString oldPath, QString new
request->deleteLater();
});
#endif
request->start();
}
void AssetMappingsScriptingInterface::setBakingEnabled(QStringList paths, bool enabled, QJSValue callback) {
auto assetClient = DependencyManager::get<AssetClient>();
auto request = assetClient->createSetBakingEnabledRequest(paths, enabled);
#ifndef ANDROID
// TODO: just to make android compile
connect(request, &SetBakingEnabledRequest::finished, this, [this, callback](SetBakingEnabledRequest* request) mutable {
if (callback.isCallable()) {
QJSValueList args{ request->getErrorString() };
@ -204,7 +214,7 @@ void AssetMappingsScriptingInterface::setBakingEnabled(QStringList paths, bool e
request->deleteLater();
});
#endif
request->start();
}

View file

@ -15,7 +15,9 @@
#include <AudioClient.h>
#include <QObject>
#include <QFuture>
#ifdef ANDROID
#include <QTcpSocket>
#endif
class LimitlessConnection : public QObject {
Q_OBJECT
public:

View file

@ -42,8 +42,9 @@ using namespace std;
static Stats* INSTANCE{ nullptr };
#ifndef ANDROID
QString getTextureMemoryPressureModeString();
#endif
Stats* Stats::getInstance() {
if (!INSTANCE) {
Stats::registerType();
@ -359,7 +360,9 @@ void Stats::updateStats(bool force) {
STAT_UPDATE(gpuTextureResourceMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourceGPUMemSize()));
STAT_UPDATE(gpuTextureResourcePopulatedMemory, (int)BYTES_TO_MB(gpu::Context::getTextureResourcePopulatedGPUMemSize()));
STAT_UPDATE(gpuTextureExternalMemory, (int)BYTES_TO_MB(gpu::Context::getTextureExternalGPUMemSize()));
#ifndef ANDROID
STAT_UPDATE(gpuTextureMemoryPressureState, getTextureMemoryPressureModeString());
#endif
STAT_UPDATE(gpuFreeMemory, (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemSize()));
STAT_UPDATE(rectifiedTextureCount, (int)RECTIFIED_TEXTURE_COUNT.load());
STAT_UPDATE(decimatedTextureCount, (int)DECIMATED_TEXTURE_COUNT.load());

View file

@ -117,7 +117,7 @@ Avatar::Avatar(QThread* thread) :
}
Avatar::~Avatar() {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) {
entityTree->withWriteLock([&] {
@ -1287,7 +1287,7 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) {
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
_moving = glm::distance(oldPosition, getWorldPosition()) > MOVE_DISTANCE_THRESHOLD;
if (_moving) {
addPhysicsFlags(Simulation::DIRTY_POSITION);
addPhysicsFlags(Simulation::DIRTY_POSITION);
}
if (_moving || _hasNewJointData) {
locationChanged();

View file

@ -891,10 +891,14 @@ void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer ne
GLuint fbo[2] {0, 0};
// need mipmaps for blitting texture
glGenerateTextureMipmap(sourceTexture);
#ifndef ANDROID
glGenerateTextureMipmap(sourceTexture);
#endif
// create 2 fbos (one for initial texture, second for scaled one)
glCreateFramebuffers(2, fbo);
#ifndef ANDROID
glCreateFramebuffers(2, fbo);
#endif
// setup source fbo
glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);
@ -924,7 +928,9 @@ void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer ne
} else {
newY = (target->height() - newHeight) / 2;
}
glBlitNamedFramebuffer(fbo[0], fbo[1], 0, 0, texWidth, texHeight, newX, newY, newX + newWidth, newY + newHeight, GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT, GL_NEAREST);
#ifndef ANDROID
glBlitNamedFramebuffer(fbo[0], fbo[1], 0, 0, texWidth, texHeight, newX, newY, newX + newWidth, newY + newHeight, GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT, GL_NEAREST);
#endif
// don't delete the textures!
glDeleteFramebuffers(2, fbo);

View file

@ -15,4 +15,5 @@ include_hifi_library_headers(avatars)
include_hifi_library_headers(controllers)
target_bullet()
target_polyvox()
target_polyvox()

View file

@ -19,6 +19,7 @@
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptString>
#include <QtScript/QScriptValue>
#include <QDateTime>
class ScriptEngine;

View file

@ -17,6 +17,7 @@
#include "ConsoleScriptingInterface.h"
#include "ScriptEngine.h"
#include <QDateTime>
#define INDENTATION 4 // 1 Tab - 4 spaces
const QString LINE_SEPARATOR = "\n ";

View file

@ -282,7 +282,7 @@ private:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
#if !defined(Q_OS_ANDROID)
#ifndef ANDROID
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f);
#endif
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f);
@ -450,7 +450,7 @@ void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) {
if (!javaScriptToInject.isEmpty()) {
rootContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
}
#if !defined(Q_OS_ANDROID)
#ifndef ANDROID
rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext));
rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext));
#endif
@ -554,7 +554,9 @@ void OffscreenQmlSurface::render() {
GLuint texture = offscreenTextures.getNextTexture(_size);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
#ifndef ANDROID
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
#endif
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
_renderControl->render();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

View file

@ -16,13 +16,18 @@
#if !defined(Q_OS_ANDROID)
static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine";
FileTypeProfile::FileTypeProfile(QObject* parent) :
QQuickWebEngineProfile(parent)
FileTypeProfile::FileTypeProfile(QObject* parent)
#ifndef ANDROID
: QQuickWebEngineProfile(parent)
#endif
{
static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)";
#ifndef ANDROID
setHttpUserAgent(WEB_ENGINE_USER_AGENT);
auto requestInterceptor = new FileTypeRequestInterceptor(this);
setRequestInterceptor(requestInterceptor);
#endif
}
#endif

View file

@ -22,4 +22,4 @@ void FileTypeRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info
RequestFilters::interceptFileType(info);
}
#endif
#endif

View file

@ -11,14 +11,26 @@
#ifndef hifi_HFTabletWebEngineRequestInterceptor_h
#define hifi_HFTabletWebEngineRequestInterceptor_h
#include <QtCore/QObject>
#ifndef ANDROID
#include <QWebEngineUrlRequestInterceptor>
#endif
class HFTabletWebEngineRequestInterceptor : public QWebEngineUrlRequestInterceptor {
class HFTabletWebEngineRequestInterceptor
#ifndef ANDROID
: public QWebEngineUrlRequestInterceptor
#endif
{
public:
HFTabletWebEngineRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {};
virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override;
HFTabletWebEngineRequestInterceptor(QObject* parent)
#ifndef ANDROID
: QWebEngineUrlRequestInterceptor(parent)
#endif
{};
#ifndef ANDROID
virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override;
#endif
};
#endif // hifi_HFWebEngineRequestInterceptor_h

View file

@ -17,14 +17,16 @@
static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine";
HFWebEngineProfile::HFWebEngineProfile(QObject* parent) :
QQuickWebEngineProfile(parent)
HFWebEngineProfile::HFWebEngineProfile(QObject* parent)
#ifndef ANDROID
: QQuickWebEngineProfile(parent)
#endif
{
setStorageName(QML_WEB_ENGINE_STORAGE_NAME);
#ifndef ANDROID setStorageName(QML_WEB_ENGINE_STORAGE_NAME);
// we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user
auto requestInterceptor = new HFWebEngineRequestInterceptor(this);
auto requestInterceptor = new HFWebEngineRequestInterceptor(this);
setRequestInterceptor(requestInterceptor);
#endif
}
#endif

View file

@ -22,4 +22,4 @@ void HFWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& i
RequestFilters::interceptHFWebEngineRequest(info);
}
#endif
#endif

View file

@ -79,4 +79,4 @@ void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info) {
info.setHttpHeader(CONTENT_HEADER.toLocal8Bit(), TYPE_VALUE.toLocal8Bit());
}
}
#endif
#endif