From fc86525863df9eb5a2a01c948fa08f30e6cf616d Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 4 Apr 2018 12:00:06 +0200 Subject: [PATCH 001/201] Cleaned up a bit shadow map clear --- libraries/render-utils/src/RenderShadowTask.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 69c5b3c689..faa5889307 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -149,9 +149,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con batch.setStateScissorRect(viewport); batch.setFramebuffer(fbo); - batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH, - vec4(vec3(1.0, 1.0, 1.0), 0.0), 1.0, 0, true); + batch.clearDepthFramebuffer(1.0, false); glm::mat4 projMat; Transform viewMat; From 573f399023eafd625212284f827ea3207f006f76 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Fri, 6 Apr 2018 14:45:16 +0200 Subject: [PATCH 002/201] Fixed incorrect shadow frustum far clip computation due to not taking into account shadow receivers --- .../render-utils/src/RenderShadowTask.cpp | 27 ++++++++++-------- libraries/render-utils/src/RenderShadowTask.h | 2 +- libraries/render/src/render/CullTask.cpp | 28 +++++++++++++------ libraries/render/src/render/CullTask.h | 2 +- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index faa5889307..fbb4bba263 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -230,12 +230,11 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene - // Enable models to not cast shadows (otherwise, models will always cast shadows) - static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withShadowCaster(); + static const auto shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); - const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); + const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterReceiverFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); - const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); + const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterReceiverFilter).asVarying(); const auto shadowItems = task.addJob("FetchShadowSelection", selectionInputs); // Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not @@ -259,21 +258,22 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende char jobName[64]; sprintf(jobName, "ShadowCascadeSetup%d", i); const auto cascadeSetupOutput = task.addJob(jobName, i, _cullFunctor, tagBits, tagMask); - const auto shadowFilter = cascadeSetupOutput.getN(0); + const auto shadowRenderFilter = cascadeSetupOutput.getN(0); + const auto shadowBoundsFilter = cascadeSetupOutput.getN(1); auto antiFrustum = render::Varying(ViewFrustumPointer()); - cascadeFrustums[i] = cascadeSetupOutput.getN(1); + cascadeFrustums[i] = cascadeSetupOutput.getN(2); if (i > 1) { antiFrustum = cascadeFrustums[i - 2]; } // CPU jobs: finer grained culling - const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter, antiFrustum).asVarying(); + const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowRenderFilter, shadowBoundsFilter, antiFrustum).asVarying(); const auto culledShadowItemsAndBounds = task.addJob("CullShadowCascade", cullInputs, shadowCullFunctor, RenderDetails::SHADOW); // GPU jobs: Render to shadow map sprintf(jobName, "RenderShadowMap%d", i); task.addJob(jobName, culledShadowItemsAndBounds, shapePlumber, i); - task.addJob("ShadowCascadeTeardown", shadowFilter); + task.addJob("ShadowCascadeTeardown", shadowRenderFilter); } task.addJob("ShadowTeardown", setupOutput); @@ -404,7 +404,11 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask).withShadowCaster(); + auto baseFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask); + // Second item filter is to filter items to keep in shadow frustum computation (here we need to keep shadow receivers) + output.edit1() = baseFilter; + // First item filter is to filter items to render in shadow map (so only keep casters) + output.edit0() = baseFilter.withShadowCaster(); // Set the keylight render args auto& cascade = globalShadow->getCascade(_cascadeIndex); @@ -417,10 +421,11 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon texelSize *= minTexelCount; _cullFunctor._minSquareSize = texelSize * texelSize; - output.edit1() = cascadeFrustum; + output.edit2() = cascadeFrustum; } else { output.edit0() = ItemFilter::Builder::nothing(); - output.edit1() = ViewFrustumPointer(); + output.edit1() = ItemFilter::Builder::nothing(); + output.edit2() = ViewFrustumPointer(); } } diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 98b70c0c9f..19ffcb4234 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -118,7 +118,7 @@ private: class RenderShadowCascadeSetup { public: - using Outputs = render::VaryingSet2; + using Outputs = render::VaryingSet3; using JobModel = render::Job::ModelO; RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) : diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index f04427540a..b5819f114f 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -368,17 +368,19 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input RenderArgs* args = renderContext->args; const auto& inShapes = inputs.get0(); - const auto& filter = inputs.get1(); - const auto& antiFrustum = inputs.get2(); + const auto& cullFilter = inputs.get1(); + const auto& boundsFilter = inputs.get2(); + const auto& antiFrustum = inputs.get3(); auto& outShapes = outputs.edit0(); auto& outBounds = outputs.edit1(); outShapes.clear(); outBounds = AABox(); - if (!filter.selectsNothing()) { + if (!cullFilter.selectsNothing() || !boundsFilter.selectsNothing()) { auto& details = args->_details.edit(_detailType); Test test(_cullFunctor, args, details, antiFrustum); + auto scene = args->_scene; for (auto& inItems : inShapes) { auto key = inItems.first; @@ -393,16 +395,26 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input if (antiFrustum == nullptr) { for (auto& item : inItems.second) { if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) { - outItems->second.emplace_back(item); - outBounds += item.bound; + const auto shapeKey = scene->getItem(item.id).getKey(); + if (cullFilter.test(shapeKey)) { + outItems->second.emplace_back(item); + } + if (boundsFilter.test(shapeKey)) { + outBounds += item.bound; + } } } } else { for (auto& item : inItems.second) { if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) { - outItems->second.emplace_back(item); - outBounds += item.bound; - } + const auto shapeKey = scene->getItem(item.id).getKey(); + if (cullFilter.test(shapeKey)) { + outItems->second.emplace_back(item); + } + if (boundsFilter.test(shapeKey)) { + outBounds += item.bound; + } + } } } details._rendered += (int)outItems->second.size(); diff --git a/libraries/render/src/render/CullTask.h b/libraries/render/src/render/CullTask.h index 3c5a30de89..47abe8a960 100644 --- a/libraries/render/src/render/CullTask.h +++ b/libraries/render/src/render/CullTask.h @@ -110,7 +110,7 @@ namespace render { class CullShapeBounds { public: - using Inputs = render::VaryingSet3; + using Inputs = render::VaryingSet4; using Outputs = render::VaryingSet2; using JobModel = Job::ModelIO; From 120de92c9e284bf5b792bc8d85845536583204f8 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Wed, 18 Apr 2018 15:52:02 -0300 Subject: [PATCH 003/201] Add login activity --- android/app/src/main/AndroidManifest.xml | 9 +- android/app/src/main/cpp/native.cpp | 76 ++++- .../hifiinterface/GotoActivity.java | 14 +- .../hifiinterface/HomeActivity.java | 105 +++--- .../hifiinterface/InterfaceActivity.java | 8 +- .../hifiinterface/LoginActivity.java | 106 ++++++ .../hifiinterface/PermissionChecker.java | 2 +- .../QtPreloader/QtPreloader.java | 315 ------------------ .../hifiinterface/SplashActivity.java | 32 ++ .../qt5/android/bindings/QtActivity.java | 8 +- .../app/src/main/res/drawable/hifi_header.xml | 50 +++ .../src/main/res/drawable/rounded_button.xml | 24 ++ .../src/main/res/drawable/rounded_edit.xml | 7 + .../app/src/main/res/layout/activity_goto.xml | 4 +- .../app/src/main/res/layout/activity_home.xml | 25 +- .../src/main/res/layout/activity_login.xml | 106 ++++++ .../src/main/res/layout/activity_splash.xml | 12 + android/app/src/main/res/values/colors.xml | 5 + android/app/src/main/res/values/strings.xml | 7 + android/app/src/main/res/values/styles.xml | 9 +- interface/src/AndroidHelper.cpp | 29 ++ interface/src/AndroidHelper.h | 16 +- interface/src/Application.cpp | 4 + 23 files changed, 577 insertions(+), 396 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java delete mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java create mode 100644 android/app/src/main/res/drawable/hifi_header.xml create mode 100644 android/app/src/main/res/drawable/rounded_button.xml create mode 100644 android/app/src/main/res/drawable/rounded_edit.xml create mode 100644 android/app/src/main/res/layout/activity_login.xml create mode 100644 android/app/src/main/res/layout/activity_splash.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ed9caee58b..8828335cd1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -47,8 +47,9 @@ - + android:theme="@style/AppTheme" /> + + + diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index c71be76b3e..3fad01db9b 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -22,7 +22,9 @@ #include #include "AndroidHelper.h" -QAndroidJniObject __activity; +QAndroidJniObject __interfaceActivity; +QAndroidJniObject __loginActivity; +QAndroidJniObject __loadCompleteListener; void tempMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (!message.isEmpty()) { @@ -142,16 +144,16 @@ void unpackAndroidAssets() { 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(); g_assetManager = AAssetManager_fromJava(env, asset_mgr); - __activity = QAndroidJniObject(instance); + qRegisterMetaType("QAndroidJniObject"); + __interfaceActivity = QAndroidJniObject(instance); auto oldMessageHandler = qInstallMessageHandler(tempMessageHandler); unpackAndroidAssets(); qInstallMessageHandler(oldMessageHandler); QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a) { QAndroidJniObject string = QAndroidJniObject::fromString(a); - __activity.callMethod("openGotoActivity", "(Ljava/lang/String;)V", string.object()); + __interfaceActivity.callMethod("openGotoActivity", "(Ljava/lang/String;)V", string.object()); }); } @@ -167,15 +169,12 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUr } 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(); } JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoBackFromAndroidActivity(JNIEnv *env, jobject instance) { @@ -204,4 +203,67 @@ JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_getCurren return env->NewStringUTF(str.toLatin1().data()); } +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_LoginActivity_nativeLogin(JNIEnv *env, jobject instance, + jstring username_, jstring password_) { + const char *c_username = env->GetStringUTFChars(username_, 0); + const char *c_password = env->GetStringUTFChars(password_, 0); + QString username = QString(c_username); + QString password = QString(c_password); + env->ReleaseStringUTFChars(username_, c_username); + env->ReleaseStringUTFChars(password_, c_password); + + QSharedPointer accountManager = AndroidHelper::instance().getAccountManager(); + + __loginActivity = QAndroidJniObject(instance); + + QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) { + AndroidHelper::instance().notifyLoginComplete(true); + }); + + QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() { + AndroidHelper::instance().notifyLoginComplete(false); + }); + + QObject::connect(&AndroidHelper::instance(), &AndroidHelper::loginComplete, [](bool success) { + jboolean jSuccess = (jboolean) success; + __loginActivity.callMethod("handleLoginCompleted", "(Z)V", jSuccess); + }); + + QMetaObject::invokeMethod(accountManager.data(), "requestAccessToken", Q_ARG(const QString&, username), Q_ARG(const QString&, password)); +} + +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(JNIEnv *env, + jobject instance) { + + __loadCompleteListener = QAndroidJniObject(instance); + + QObject::connect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, []() { + + __loadCompleteListener.callMethod("onAppLoadedComplete", "()V"); + __interfaceActivity.callMethod("onAppLoadedComplete", "()V"); + + QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, nullptr, + nullptr); + }); + +} +JNIEXPORT jboolean JNICALL +Java_io_highfidelity_hifiinterface_HomeActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) { + return AndroidHelper::instance().getAccountManager()->isLoggedIn(); +} + +JNIEXPORT void JNICALL +Java_io_highfidelity_hifiinterface_HomeActivity_nativeLogout(JNIEnv *env, jobject instance) { + AndroidHelper::instance().getAccountManager()->logout(); +} + +JNIEXPORT jstring JNICALL +Java_io_highfidelity_hifiinterface_HomeActivity_nativeGetDisplayName(JNIEnv *env, + jobject instance) { + QString username = AndroidHelper::instance().getAccountManager()->getAccountInfo().getUsername(); + return env->NewStringUTF(username.toLatin1().data()); +} + } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java index 65221bc21c..f1ecc21d84 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -9,7 +9,6 @@ import android.support.v7.widget.Toolbar; import android.view.KeyEvent; import android.view.MenuItem; import android.view.View; -import android.view.inputmethod.EditorInfo; import android.widget.EditText; import java.net.URI; @@ -17,6 +16,8 @@ import java.net.URISyntaxException; public class GotoActivity extends AppCompatActivity { + public static final String PARAM_DOMAIN_URL = "domain_url"; + private EditText mUrlEditText; private AppCompatButton mGoBtn; @@ -69,15 +70,10 @@ public class GotoActivity extends AppCompatActivity { urlString = "hifi://" + urlString; } - Intent intent = new Intent(this, InterfaceActivity.class); - intent.putExtra(InterfaceActivity.DOMAIN_URL, urlString); + Intent intent = new Intent(); + intent.putExtra(GotoActivity.PARAM_DOMAIN_URL, urlString); + setResult(RESULT_OK, intent); finish(); - if (getIntent() != null && - getIntent().hasExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY) && - getIntent().getBooleanExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - } - startActivity(intent); } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java index ed442e2d8d..117e1f2cc4 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java @@ -1,9 +1,7 @@ package io.highfidelity.hifiinterface; -import android.app.ProgressDialog; import android.content.Intent; import android.graphics.Color; -import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.NavigationView; @@ -22,18 +20,23 @@ import android.widget.TabHost; import android.widget.TabWidget; import android.widget.TextView; -import io.highfidelity.hifiinterface.QtPreloader.QtPreloader; import io.highfidelity.hifiinterface.view.DomainAdapter; public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { + public native boolean nativeIsLoggedIn(); + public native void nativeLogout(); + public native String nativeGetDisplayName(); + /** * Set this intent extra param to NOT start a new InterfaceActivity after a domain is selected" */ - public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity"; + //public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity"; + + public static final int ENTER_DOMAIN_URL = 1; + private DomainAdapter domainAdapter; private DrawerLayout mDrawerLayout; - private ProgressDialog mDialog; private NavigationView mNavigationView; @Override @@ -89,15 +92,7 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On @Override public void onItemClick(View view, int position, DomainAdapter.Domain domain) { - Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class); - intent.putExtra(InterfaceActivity.DOMAIN_URL, domain.url); - HomeActivity.this.finish(); - if (getIntent() != null && - getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) && - getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - } - startActivity(intent); + gotoDomain(domain.url); } }); domainsView.setAdapter(domainAdapter); @@ -112,49 +107,28 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On searchTextView.setTextAppearance(R.style.SearchText); } - if (getIntent() == null || - !getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) || - !getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) { - preloadQt(); - showActivityIndicator(); - } + updateLoginMenu(); } - private void showActivityIndicator() { - if (mDialog == null) { - mDialog = new ProgressDialog(this); - } - mDialog.setMessage("Please wait..."); - mDialog.setCancelable(false); - mDialog.show(); - } - - private void cancelActivityIndicator() { - if (mDialog != null) { - mDialog.cancel(); + private void updateLoginMenu() { + TextView loginOption = findViewById(R.id.login); + TextView logoutOption = findViewById(R.id.logout); + if (nativeIsLoggedIn()) { + loginOption.setVisibility(View.GONE); + logoutOption.setVisibility(View.VISIBLE); + } else { + loginOption.setVisibility(View.VISIBLE); + logoutOption.setVisibility(View.GONE); } } - private AsyncTask preloadTask; - - private void preloadQt() { - if (preloadTask == null) { - preloadTask = new AsyncTask() { - @Override - protected Object doInBackground(Object[] objects) { - new QtPreloader(HomeActivity.this).initQt(); - runOnUiThread(new Runnable() { - @Override - public void run() { - cancelActivityIndicator(); - } - }); - return null; - } - }; - preloadTask.execute(); - } + private void gotoDomain(String domainUrl) { + Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class); + intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl); + HomeActivity.this.finish(); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(intent); } @Override @@ -181,7 +155,6 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On @Override protected void onDestroy() { - cancelActivityIndicator(); super.onDestroy(); } @@ -190,11 +163,33 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On switch(item.getItemId()) { case R.id.action_goto: Intent i = new Intent(this, GotoActivity.class); - startActivity(i); - return true; - case R.id.action_settings: + startActivityForResult(i, ENTER_DOMAIN_URL); return true; } return false; } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == ENTER_DOMAIN_URL && resultCode == RESULT_OK) { + gotoDomain(data.getStringExtra(GotoActivity.PARAM_DOMAIN_URL)); + } + } + + @Override + protected void onStart() { + super.onStart(); + updateLoginMenu(); + } + + public void onLoginClicked(View view) { + Intent intent = new Intent(this, LoginActivity.class); + startActivity(intent); + } + + public void onLogoutClicked(View view) { + nativeLogout(); + updateLoginMenu(); + } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index 678f7e8aac..2b86abc9da 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -63,6 +63,7 @@ public class InterfaceActivity extends QtActivity { @Override public void onCreate(Bundle savedInstanceState) { + super.isLoading = true; Intent intent = getIntent(); if (intent.hasExtra(DOMAIN_URL) && !intent.getStringExtra(DOMAIN_URL).isEmpty()) { intent.putExtra("applicationArguments", "--url "+intent.getStringExtra(DOMAIN_URL)); @@ -112,6 +113,8 @@ public class InterfaceActivity extends QtActivity { } } }); + startActivity(new Intent(this, SplashActivity.class)); + } @Override @@ -201,7 +204,6 @@ public class InterfaceActivity extends QtActivity { switch (activityName) { case "Goto": { Intent intent = new Intent(this, HomeActivity.class); - intent.putExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, true); startActivity(intent); break; } @@ -212,4 +214,8 @@ public class InterfaceActivity extends QtActivity { } } + public void onAppLoadedComplete() { + super.isLoading = false; + } + } \ No newline at end of file diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java new file mode 100644 index 0000000000..9fd256aa82 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java @@ -0,0 +1,106 @@ +package io.highfidelity.hifiinterface; + +import android.app.ProgressDialog; +import android.support.annotation.MainThread; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +public class LoginActivity extends AppCompatActivity { + + public native void nativeLogin(String username, String password); + + private EditText mUsername; + private EditText mPassword; + private TextView mError; + private Button mLoginButton; + + private ProgressDialog mDialog; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + + mUsername = findViewById(R.id.username); + mPassword = findViewById(R.id.password); + mError = findViewById(R.id.error); + mLoginButton = findViewById(R.id.loginButton); + + mPassword.setOnEditorActionListener( + new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + mLoginButton.performClick(); + return true; + } + return false; + } + }); + + } + + @Override + protected void onStop() { + super.onStop(); + cancelActivityIndicator(); + } + + public void login(View view) { + String username = mUsername.getText().toString(); + String password = mPassword.getText().toString(); + if (username.isEmpty() || password.isEmpty()) { + showError(getString(R.string.login_username_or_password_incorrect)); + } else { + mLoginButton.setEnabled(false); + hideError(); + showActivityIndicator(); + nativeLogin(username, password); + } + } + + private void showActivityIndicator() { + if (mDialog == null) { + mDialog = new ProgressDialog(this); + } + mDialog.setMessage(getString(R.string.logging_in)); + mDialog.setCancelable(false); + mDialog.show(); + } + + private void cancelActivityIndicator() { + if (mDialog != null) { + mDialog.cancel(); + } + } + private void showError(String error) { + mError.setText(error); + mError.setVisibility(View.VISIBLE); + } + + private void hideError() { + mError.setText(""); + mError.setVisibility(View.INVISIBLE); + } + + public void handleLoginCompleted(boolean success) { + runOnUiThread(() -> { + mLoginButton.setEnabled(true); + cancelActivityIndicator(); + if (success) { + finish(); + } else { + showError(getString(R.string.login_username_or_password_incorrect)); + } + }); + } + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java index b1c5f570c8..2b48d85a48 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java @@ -64,7 +64,7 @@ public class PermissionChecker extends Activity { private void launchActivityWithPermissions(){ finish(); - Intent i = new Intent(this, HomeActivity.class); + Intent i = new Intent(this, InterfaceActivity.class); startActivity(i); finish(); } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java b/android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java deleted file mode 100644 index d9ecdb9710..0000000000 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java +++ /dev/null @@ -1,315 +0,0 @@ -package io.highfidelity.hifiinterface.QtPreloader; - -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ComponentInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.AssetManager; -import android.os.Bundle; -import android.util.Log; - -import org.qtproject.qt5.android.bindings.QtApplication; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Method; -import java.util.ArrayList; - -import dalvik.system.DexClassLoader; - -/** - * Created by Gabriel Calero & Cristian Duarte on 3/22/18. - */ - -public class QtPreloader { - - public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1"; - private ComponentInfo m_contextInfo; - private String[] m_qtLibs = null; // required qt libs - private Context m_context; - - private static final String DEX_PATH_KEY = "dex.path"; - private static final String LIB_PATH_KEY = "lib.path"; - private static final String NATIVE_LIBRARIES_KEY = "native.libraries"; - private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables"; - private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries"; - private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id"; - private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id"; - private static final String MAIN_LIBRARY_KEY = "main.library"; - - private static final int BUFFER_SIZE = 1024; - - public QtPreloader(Context context) { - m_context = context; - } - - public void initQt() { - - try { - m_contextInfo = m_context.getPackageManager().getActivityInfo(new ComponentName("io.highfidelity.hifiinterface", "io.highfidelity.hifiinterface.InterfaceActivity"), - PackageManager.GET_META_DATA); - - if (m_contextInfo.metaData.containsKey("android.app.qt_libs_resource_id")) { - int resourceId = m_contextInfo.metaData.getInt("android.app.qt_libs_resource_id"); - m_qtLibs = m_context.getResources().getStringArray(resourceId); - } - ArrayList libraryList = new ArrayList<>(); - String localPrefix = m_context.getApplicationInfo().dataDir + "/"; - String pluginsPrefix = localPrefix + "qt-reserved-files/"; - cleanOldCacheIfNecessary(localPrefix, pluginsPrefix); - extractBundledPluginsAndImports(pluginsPrefix); - - for (String lib : m_qtLibs) { - libraryList.add(localPrefix + "lib/lib" + lib + ".so"); - } - - if (m_contextInfo.metaData.containsKey("android.app.load_local_libs")) { - String[] extraLibs = m_contextInfo.metaData.getString("android.app.load_local_libs").split(":"); - for (String lib : extraLibs) { - if (lib.length() > 0) { - if (lib.startsWith("lib/")) { - libraryList.add(localPrefix + lib); - } else { - libraryList.add(pluginsPrefix + lib); - } - } - } - } - - Bundle loaderParams = new Bundle(); - loaderParams.putString(DEX_PATH_KEY, new String()); - - loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList); - - loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES - + "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml" - + "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports" - + "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins"); - - - // add all bundled Qt libs to loader params - ArrayList libs = new ArrayList<>(); - - String libName = m_contextInfo.metaData.getString("android.app.lib_name"); - loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function - loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs); - - // load and start QtLoader class - DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files - m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written. - loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists) - m_context.getClassLoader()); // parent loader - - Class loaderClass = classLoader.loadClass(loaderClassName()); // load QtLoader class - Object qtLoader = loaderClass.newInstance(); // create an instance - Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication", - contextClassName(), - ClassLoader.class, - Bundle.class); - prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams); - - // now load the application library so it's accessible from this class loader - if (libName != null) { - System.loadLibrary(libName); - } - } catch (Exception e) { - Log.e(QtApplication.QtTAG, "Error pre-loading HiFi Qt app", e); - } - } - - protected String loaderClassName() { - return "org.qtproject.qt5.android.QtActivityDelegate"; - } - - protected Class contextClassName() { - return android.app.Activity.class; - } - - - private void deleteRecursively(File directory) { - File[] files = directory.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory()) { - deleteRecursively(file); - } else { - file.delete(); - } - } - - directory.delete(); - } - } - - private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) { - File newCache = new File(localPrefix); - if (!newCache.exists()) { - { - File oldPluginsCache = new File(oldLocalPrefix + "plugins/"); - if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) { - deleteRecursively(oldPluginsCache); - } - } - - { - File oldImportsCache = new File(oldLocalPrefix + "imports/"); - if (oldImportsCache.exists() && oldImportsCache.isDirectory()) { - deleteRecursively(oldImportsCache); - } - } - - { - File oldQmlCache = new File(oldLocalPrefix + "qml/"); - if (oldQmlCache.exists() && oldQmlCache.isDirectory()) { - deleteRecursively(oldQmlCache); - } - } - } - } - - static private void copyFile(InputStream inputStream, OutputStream outputStream) - throws IOException { - byte[] buffer = new byte[BUFFER_SIZE]; - - int count; - while ((count = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, count); - } - } - - private void copyAsset(String source, String destination) - throws IOException { - // Already exists, we don't have to do anything - File destinationFile = new File(destination); - if (destinationFile.exists()) { - return; - } - - File parentDirectory = destinationFile.getParentFile(); - if (!parentDirectory.exists()) { - parentDirectory.mkdirs(); - } - - destinationFile.createNewFile(); - - AssetManager assetsManager = m_context.getAssets(); - InputStream inputStream = assetsManager.open(source); - OutputStream outputStream = new FileOutputStream(destinationFile); - copyFile(inputStream, outputStream); - - inputStream.close(); - outputStream.close(); - } - - private static void createBundledBinary(String source, String destination) - throws IOException { - // Already exists, we don't have to do anything - File destinationFile = new File(destination); - if (destinationFile.exists()) { - return; - } - - File parentDirectory = destinationFile.getParentFile(); - if (!parentDirectory.exists()) { - parentDirectory.mkdirs(); - } - - destinationFile.createNewFile(); - - InputStream inputStream = new FileInputStream(source); - OutputStream outputStream = new FileOutputStream(destinationFile); - copyFile(inputStream, outputStream); - - inputStream.close(); - outputStream.close(); - } - - private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) { - File versionFile = new File(pluginsPrefix + "cache.version"); - - long cacheVersion = 0; - if (versionFile.exists() && versionFile.canRead()) { - try { - DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile)); - cacheVersion = inputStream.readLong(); - inputStream.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (cacheVersion != packageVersion) { - deleteRecursively(new File(pluginsPrefix)); - return true; - } else { - return false; - } - } - - private void extractBundledPluginsAndImports(String pluginsPrefix) throws IOException { - String libsDir = m_context.getApplicationInfo().nativeLibraryDir + "/"; - long packageVersion = -1; - try { - PackageInfo packageInfo = m_context.getPackageManager().getPackageInfo(m_context.getPackageName(), 0); - packageVersion = packageInfo.lastUpdateTime; - } catch (Exception e) { - e.printStackTrace(); - } - - - if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) { - return; - } - - { - File versionFile = new File(pluginsPrefix + "cache.version"); - - File parentDirectory = versionFile.getParentFile(); - if (!parentDirectory.exists()) { - parentDirectory.mkdirs(); - } - - versionFile.createNewFile(); - - DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile)); - outputStream.writeLong(packageVersion); - outputStream.close(); - } - - { - String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY; - if (m_contextInfo.metaData.containsKey(key)) { - String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key)); - - for (String bundledImportBinary : list) { - String[] split = bundledImportBinary.split(":"); - String sourceFileName = libsDir + split[0]; - String destinationFileName = pluginsPrefix + split[1]; - createBundledBinary(sourceFileName, destinationFileName); - } - } - } - - { - String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY; - if (m_contextInfo.metaData.containsKey(key)) { - String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key)); - - for (String fileName : list) { - String[] split = fileName.split(":"); - String sourceFileName = split[0]; - String destinationFileName = pluginsPrefix + split[1]; - copyAsset(sourceFileName, destinationFileName); - } - } - - } - } -} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java new file mode 100644 index 0000000000..b663a3e396 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java @@ -0,0 +1,32 @@ +package io.highfidelity.hifiinterface; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +public class SplashActivity extends Activity { + + private native void registerLoadCompleteListener(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_splash); + registerLoadCompleteListener(); + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onStop() { + super.onStop(); + } + + public void onAppLoadedComplete() { + startActivity(new Intent(this, HomeActivity.class)); + finish(); + } +} diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java index ed55c16cde..887d27dba4 100644 --- a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java +++ b/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java @@ -68,6 +68,8 @@ public class QtActivity extends Activity { public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme. private QtActivityLoader m_loader = new QtActivityLoader(this); + public boolean isLoading; + public QtActivity() { } @@ -499,7 +501,11 @@ public class QtActivity extends Activity { @Override protected void onPause() { super.onPause(); - QtApplication.invokeDelegate(); + // GC: this trick allow us to show a splash activity until Qt app finishes + // loading + if (!isLoading) { + QtApplication.invokeDelegate(); + } } //--------------------------------------------------------------------------- diff --git a/android/app/src/main/res/drawable/hifi_header.xml b/android/app/src/main/res/drawable/hifi_header.xml new file mode 100644 index 0000000000..9f7c85297a --- /dev/null +++ b/android/app/src/main/res/drawable/hifi_header.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/rounded_button.xml b/android/app/src/main/res/drawable/rounded_button.xml new file mode 100644 index 0000000000..11a9f90c8b --- /dev/null +++ b/android/app/src/main/res/drawable/rounded_button.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/rounded_edit.xml b/android/app/src/main/res/drawable/rounded_edit.xml new file mode 100644 index 0000000000..3c1cac4d1d --- /dev/null +++ b/android/app/src/main/res/drawable/rounded_edit.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_goto.xml b/android/app/src/main/res/layout/activity_goto.xml index 7f4e7d5fcf..2d5943e15d 100644 --- a/android/app/src/main/res/layout/activity_goto.xml +++ b/android/app/src/main/res/layout/activity_goto.xml @@ -3,8 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root_activity_goto" - android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" tools:context="io.highfidelity.hifiinterface.GotoActivity"> + > + + + + + diff --git a/android/app/src/main/res/layout/activity_login.xml b/android/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000000..ecf72b94bf --- /dev/null +++ b/android/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + +