Commit aada02df authored by Tibor Goldschwendt's avatar Tibor Goldschwendt Committed by Commit Bot

[ar] On-demand install AR module, add ModuleInstaller class

The ModuleInstaller goes into the new component module_installer, which
hosts code necessary to install dynamic feature modules. It is used by
Chrome to install the AR module (and other modules in the future). See
go/chromevr-dfm-design-proposal for more information.

Bug: 863068, 862690
Change-Id: Ib7fb7451fbd9ce08cd57e96f923a81f7327a8ddb
Reviewed-on: https://chromium-review.googlesource.com/1228308
Commit-Queue: Tibor Goldschwendt <tiborg@chromium.org>
Reviewed-by: default avatarCait Phillips <caitkp@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Reviewed-by: default avataragrieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/master@{#593190}
parent 6e1bb98a
......@@ -243,6 +243,7 @@ android_library("chrome_java") {
"//components/language/android:language_bridge_java",
"//components/location/android:location_java",
"//components/minidump_uploader:minidump_uploader_java",
"//components/module_installer/android:module_installer_java",
"//components/navigation_interception/android:navigation_interception_java",
"//components/offline_items_collection/core:core_java",
"//components/payments/content/android:java",
......
......@@ -12,6 +12,7 @@ include_rules = [
"+components/language",
"+components/location/android/java",
"+components/minidump_uploader",
"+components/module_installer/android/java/src/org/chromium/components/module_installer",
"+components/navigation_interception",
"+components/offline_items_collection/core/android/java",
"+components/payments/content/android/java/src/org/chromium/components/payments",
......
......@@ -14,6 +14,8 @@ import android.os.Build;
import android.os.Bundle;
import android.support.annotation.IntDef;
import dalvik.system.BaseDexClassLoader;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.CalledByNative;
......@@ -22,6 +24,8 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.components.module_installer.ModuleInstaller;
import org.chromium.ui.widget.Toast;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -85,6 +89,12 @@ public class ArCoreJavaUtils {
return new ArCoreJavaUtils(nativeArCoreJavaUtils);
}
@CalledByNative
private static String getArCoreShimLibraryPath() {
return ((BaseDexClassLoader) ContextUtils.getApplicationContext().getClassLoader())
.findLibrary("arcore_sdk_c_minimal");
}
private ArCoreJavaUtils(long nativeArCoreJavaUtils) {
mNativeArCoreJavaUtils = nativeArCoreJavaUtils;
initializeAppInfo();
......@@ -115,6 +125,9 @@ public class ArCoreJavaUtils {
"Application manifest must contain meta-data " + METADATA_KEY_MIN_APK_VERSION);
}
mAppInfoInitialized = true;
// Need to be called before trying to access the AR module.
ModuleInstaller.init();
}
private int getArCoreApkVersionNumber() {
......@@ -148,6 +161,27 @@ public class ArCoreJavaUtils {
return getArCoreInstallStatus() != ArCoreInstallStatus.ARCORE_INSTALLED;
}
@CalledByNative
private void requestInstallArModule() {
// TODO(crbug.com/863064): This is a placeholder UI. Replace once proper UI is spec'd.
Toast.makeText(ContextUtils.getApplicationContext(), R.string.ar_module_install_start_text,
Toast.LENGTH_SHORT)
.show();
ModuleInstaller.install("ar", success -> {
// TODO(crbug.com/863064): This is a placeholder UI. Replace once proper UI is spec'd.
int mToastTextRes = success ? R.string.ar_module_install_success_text
: R.string.ar_module_install_failure_text;
Toast.makeText(ContextUtils.getApplicationContext(), mToastTextRes, Toast.LENGTH_SHORT)
.show();
if (mNativeArCoreJavaUtils != 0) {
assert shouldRequestInstallArModule() == !success;
nativeOnRequestInstallArModuleResult(mNativeArCoreJavaUtils, success);
}
});
}
@CalledByNative
private void requestInstallSupportedArCore(final Tab tab) {
assert shouldRequestInstallSupportedArCore();
......@@ -191,5 +225,18 @@ public class ArCoreJavaUtils {
R.drawable.vr_services, infobarText, buttonText, null, true);
}
@CalledByNative
private boolean shouldRequestInstallArModule() {
try {
// Try to find class in AR module that has not been obfuscated.
Class.forName("com.google.vr.dynamite.client.UsedByNative");
return false;
} catch (ClassNotFoundException e) {
return true;
}
}
private native void nativeOnRequestInstallArModuleResult(
long nativeArCoreJavaUtils, boolean success);
private native void nativeOnRequestInstallSupportedArCoreCanceled(long nativeArCoreJavaUtils);
}
......@@ -3639,6 +3639,15 @@ However, you aren’t invisible. Going private doesn’t hide your browsing from
<message name="IDS_AR_MODULE_TITLE" desc="Title of the Augmented Reality (AR) dynamic feature module. Used, for instance, in the text of dialogs confirming to download the module.">
AR
</message>
<message name="IDS_AR_MODULE_INSTALL_START_TEXT" desc="Text shown on a toast when Chrome starts to download the AR dynamic feature module.">
Installing AR module...
</message>
<message name="IDS_AR_MODULE_INSTALL_SUCCESS_TEXT" desc="Text shown on a toast when Chrome successfully installed the AR dynamic feature module.">
Installed AR module
</message>
<message name="IDS_AR_MODULE_INSTALL_FAILURE_TEXT" desc="Text shown on a toast when Chrome failed to install the AR dynamic feature module.">
Failed to install AR module
</message>
</if>
<!-- Custom Context Menu Informations -->
......
......@@ -12,10 +12,8 @@
android:minSdkVersion="24"
android:targetSdkVersion="{{target_sdk_version}}" />
<!-- TODO(crbug.com/863068): Set dist:onDemand="true" once we can on-demand
install modules. -->
<dist:module
dist:onDemand="false"
dist:onDemand="true"
dist:title="@string/ar_module_title">
<dist:fusing dist:include="false" />
</dist:module>
......
......@@ -177,6 +177,9 @@ void ARCoreDevice::RequestArModule(int render_process_id,
int render_frame_id,
bool has_user_activation) {
if (arcore_java_utils_->ShouldRequestInstallArModule()) {
on_request_ar_module_result_callback_ =
base::BindOnce(&ARCoreDevice::OnRequestArModuleResult, GetWeakPtr(),
render_process_id, render_frame_id, has_user_activation);
arcore_java_utils_->RequestInstallArModule();
return;
}
......@@ -235,6 +238,14 @@ void ARCoreDevice::RequestArCoreInstallOrUpdate(int render_process_id,
has_user_activation, true);
}
void ARCoreDevice::OnRequestInstallArModuleResult(bool success) {
DCHECK(IsOnMainThread());
if (on_request_ar_module_result_callback_) {
std::move(on_request_ar_module_result_callback_).Run(success);
}
}
void ARCoreDevice::OnRequestInstallSupportedARCoreCanceled() {
DCHECK(IsOnMainThread());
DCHECK(is_arcore_gl_thread_initialized_);
......
......@@ -46,6 +46,7 @@ class ARCoreDevice : public VRDeviceBase {
return weak_ptr_factory_.GetWeakPtr();
}
void OnRequestInstallArModuleResult(bool success);
void OnRequestInstallSupportedARCoreCanceled();
private:
......@@ -136,6 +137,7 @@ class ARCoreDevice : public VRDeviceBase {
base::OnceCallback<void(bool)>
on_request_arcore_install_or_update_result_callback_;
base::OnceCallback<void(bool)> on_request_ar_module_result_callback_;
// Must be last.
base::WeakPtrFactory<ARCoreDevice> weak_ptr_factory_;
......
......@@ -4,6 +4,7 @@
#include "chrome/browser/android/vr/arcore_device/arcore_java_utils.h"
#include "base/android/jni_string.h"
#include "chrome/browser/android/vr/arcore_device/arcore_device.h"
#include "chrome/browser/android/vr/arcore_device/arcore_shim.h"
#include "jni/ArCoreJavaUtils_jni.h"
......@@ -23,7 +24,11 @@ bool ArCoreJavaUtils::EnsureLoaded() {
if (!Java_ArCoreJavaUtils_shouldLoadArCoreSdk(env))
return false;
return LoadArCoreSdk();
// TODO(crbug.com/884780): Allow loading the ArCore shim by name instead of by
// absolute path.
ScopedJavaLocalRef<jstring> java_path =
Java_ArCoreJavaUtils_getArCoreShimLibraryPath(env);
return LoadArCoreSdk(base::android::ConvertJavaStringToUTF8(env, java_path));
}
ArCoreJavaUtils::ArCoreJavaUtils(device::ARCoreDevice* arcore_device)
......@@ -52,12 +57,13 @@ void ArCoreJavaUtils::OnRequestInstallSupportedArCoreCanceled(
}
bool ArCoreJavaUtils::ShouldRequestInstallArModule() {
// TODO(crbug.com/863068): Check whether AR module is already installed.
return false;
return Java_ArCoreJavaUtils_shouldRequestInstallArModule(
AttachCurrentThread(), j_arcore_java_utils_);
}
void ArCoreJavaUtils::RequestInstallArModule() {
// TODO(crbug.com/863068): On-demand install AR module.
Java_ArCoreJavaUtils_requestInstallArModule(AttachCurrentThread(),
j_arcore_java_utils_);
}
bool ArCoreJavaUtils::ShouldRequestInstallSupportedArCore() {
......@@ -74,4 +80,11 @@ void ArCoreJavaUtils::RequestInstallSupportedArCore(
j_tab_android);
}
void ArCoreJavaUtils::OnRequestInstallArModuleResult(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
bool success) {
arcore_device_->OnRequestInstallArModuleResult(success);
}
} // namespace vr
......@@ -27,7 +27,11 @@ class ArCoreJavaUtils {
void RequestInstallSupportedArCore(
base::android::ScopedJavaLocalRef<jobject> j_tab_android);
// Method called from the Java side
// Methods called from the Java side.
void OnRequestInstallArModuleResult(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
bool success);
void OnRequestInstallSupportedArCoreCanceled(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
......
......@@ -66,11 +66,11 @@ void LoadFunction(void* handle, const char* function_name, Fn* fn_out) {
namespace vr {
bool LoadArCoreSdk() {
bool LoadArCoreSdk(const std::string& libraryPath) {
if (arcore_api)
return true;
sdk_handle = dlopen("libarcore_sdk_c_minimal.so", RTLD_GLOBAL | RTLD_NOW);
sdk_handle = dlopen(libraryPath.c_str(), RTLD_GLOBAL | RTLD_NOW);
if (!sdk_handle) {
char* error_string = nullptr;
error_string = dlerror();
......
......@@ -8,7 +8,7 @@
namespace vr {
// TODO(vollick): add support for unloading the SDK.
bool LoadArCoreSdk();
bool LoadArCoreSdk(const std::string& libraryPath);
} // namespace vr
......
agrieve@chromium.org
tiborg@chromium.org
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/android/rules.gni")
android_library("module_installer_java") {
java_files = [
"java/src/org/chromium/components/module_installer/ModuleInstaller.java",
]
deps = [
"//base:base_java",
"//third_party/android_deps:com_google_android_play_core_java",
]
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.module_installer;
import com.google.android.play.core.splitcompat.SplitCompat;
import com.google.android.play.core.splitinstall.SplitInstallManager;
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory;
import com.google.android.play.core.splitinstall.SplitInstallRequest;
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener;
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/** Installs Dynamic Feature Modules (DFMs). */
public class ModuleInstaller {
private static final String TAG = "ModuleInstaller";
private static final Map<String, List<OnFinishedListener>> sModuleNameListenerMap =
new HashMap<>();
private static SplitInstallManager sManager;
private static SplitInstallStateUpdatedListener sUpdateListener;
/** Listener for when a module install has finished. */
public interface OnFinishedListener {
/**
* Called when the install has finished.
*
* @param success True if the module was installed successfully.
*/
void onFinished(boolean success);
}
/** Needs to be called before trying to access a module. */
public static void init() {
SplitCompat.install(ContextUtils.getApplicationContext());
}
/**
* Requests the install of a module. The install will be performed asynchronously.
*
* @param moduleName Name of the module as defined in GN.
* @param onFinishedListener Listener to be called once installation is finished.
*/
public static void install(String moduleName, OnFinishedListener onFinishedListener) {
ThreadUtils.assertOnUiThread();
if (!sModuleNameListenerMap.containsKey(moduleName)) {
sModuleNameListenerMap.put(moduleName, new LinkedList<>());
}
List<OnFinishedListener> onFinishedListeners = sModuleNameListenerMap.get(moduleName);
onFinishedListeners.add(onFinishedListener);
if (onFinishedListeners.size() > 1) {
// Request is already running.
return;
}
SplitInstallRequest request =
SplitInstallRequest.newBuilder().addModule(moduleName).build();
getManager().startInstall(request).addOnFailureListener(exception -> {
Log.e(TAG, "Failed to request module '" + moduleName + "': " + exception);
onFinished(false, Arrays.asList(moduleName));
});
}
private static void onFinished(boolean success, List<String> moduleNames) {
ThreadUtils.assertOnUiThread();
for (String moduleName : moduleNames) {
List<OnFinishedListener> onFinishedListeners = sModuleNameListenerMap.get(moduleName);
if (onFinishedListeners == null) continue;
for (OnFinishedListener listener : onFinishedListeners) {
listener.onFinished(success);
}
sModuleNameListenerMap.remove(moduleName);
}
if (sModuleNameListenerMap.isEmpty()) {
sManager.unregisterListener(sUpdateListener);
sUpdateListener = null;
sManager = null;
}
}
private static SplitInstallManager getManager() {
ThreadUtils.assertOnUiThread();
if (sManager == null) {
sManager = SplitInstallManagerFactory.create(ContextUtils.getApplicationContext());
sUpdateListener = (state) -> {
switch (state.status()) {
case SplitInstallSessionStatus.INSTALLED:
onFinished(true, state.moduleNames());
break;
case SplitInstallSessionStatus.CANCELED:
case SplitInstallSessionStatus.FAILED:
Log.e(TAG,
"Failed to install modules '" + state.moduleNames()
+ "': " + state.status());
onFinished(false, state.moduleNames());
break;
}
};
sManager.registerListener(sUpdateListener);
}
return sManager;
}
private ModuleInstaller() {}
}
......@@ -66,6 +66,7 @@
"chrome/android/webapk/strings/android_webapk_strings.grd",
"components/autofill/android/java/strings/autofill_strings.grd",
"components/embedder_support/android/java/strings/web_contents_delegate_android_strings.grd",
"components/module_installer/android/java/strings/module_installer_strings.grd",
"content/public/android/java/strings/android_content_strings.grd",
"ui/android/java/strings/android_ui_strings.grd",
],
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment