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") { ...@@ -243,6 +243,7 @@ android_library("chrome_java") {
"//components/language/android:language_bridge_java", "//components/language/android:language_bridge_java",
"//components/location/android:location_java", "//components/location/android:location_java",
"//components/minidump_uploader:minidump_uploader_java", "//components/minidump_uploader:minidump_uploader_java",
"//components/module_installer/android:module_installer_java",
"//components/navigation_interception/android:navigation_interception_java", "//components/navigation_interception/android:navigation_interception_java",
"//components/offline_items_collection/core:core_java", "//components/offline_items_collection/core:core_java",
"//components/payments/content/android:java", "//components/payments/content/android:java",
......
...@@ -12,6 +12,7 @@ include_rules = [ ...@@ -12,6 +12,7 @@ include_rules = [
"+components/language", "+components/language",
"+components/location/android/java", "+components/location/android/java",
"+components/minidump_uploader", "+components/minidump_uploader",
"+components/module_installer/android/java/src/org/chromium/components/module_installer",
"+components/navigation_interception", "+components/navigation_interception",
"+components/offline_items_collection/core/android/java", "+components/offline_items_collection/core/android/java",
"+components/payments/content/android/java/src/org/chromium/components/payments", "+components/payments/content/android/java/src/org/chromium/components/payments",
......
...@@ -14,6 +14,8 @@ import android.os.Build; ...@@ -14,6 +14,8 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import dalvik.system.BaseDexClassLoader;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
...@@ -22,6 +24,8 @@ import org.chromium.chrome.R; ...@@ -22,6 +24,8 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.infobar.InfoBarIdentifier; import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder; import org.chromium.chrome.browser.infobar.SimpleConfirmInfoBarBuilder;
import org.chromium.chrome.browser.tab.Tab; 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.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
...@@ -85,6 +89,12 @@ public class ArCoreJavaUtils { ...@@ -85,6 +89,12 @@ public class ArCoreJavaUtils {
return new ArCoreJavaUtils(nativeArCoreJavaUtils); return new ArCoreJavaUtils(nativeArCoreJavaUtils);
} }
@CalledByNative
private static String getArCoreShimLibraryPath() {
return ((BaseDexClassLoader) ContextUtils.getApplicationContext().getClassLoader())
.findLibrary("arcore_sdk_c_minimal");
}
private ArCoreJavaUtils(long nativeArCoreJavaUtils) { private ArCoreJavaUtils(long nativeArCoreJavaUtils) {
mNativeArCoreJavaUtils = nativeArCoreJavaUtils; mNativeArCoreJavaUtils = nativeArCoreJavaUtils;
initializeAppInfo(); initializeAppInfo();
...@@ -115,6 +125,9 @@ public class ArCoreJavaUtils { ...@@ -115,6 +125,9 @@ public class ArCoreJavaUtils {
"Application manifest must contain meta-data " + METADATA_KEY_MIN_APK_VERSION); "Application manifest must contain meta-data " + METADATA_KEY_MIN_APK_VERSION);
} }
mAppInfoInitialized = true; mAppInfoInitialized = true;
// Need to be called before trying to access the AR module.
ModuleInstaller.init();
} }
private int getArCoreApkVersionNumber() { private int getArCoreApkVersionNumber() {
...@@ -148,6 +161,27 @@ public class ArCoreJavaUtils { ...@@ -148,6 +161,27 @@ public class ArCoreJavaUtils {
return getArCoreInstallStatus() != ArCoreInstallStatus.ARCORE_INSTALLED; 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 @CalledByNative
private void requestInstallSupportedArCore(final Tab tab) { private void requestInstallSupportedArCore(final Tab tab) {
assert shouldRequestInstallSupportedArCore(); assert shouldRequestInstallSupportedArCore();
...@@ -191,5 +225,18 @@ public class ArCoreJavaUtils { ...@@ -191,5 +225,18 @@ public class ArCoreJavaUtils {
R.drawable.vr_services, infobarText, buttonText, null, true); 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); private native void nativeOnRequestInstallSupportedArCoreCanceled(long nativeArCoreJavaUtils);
} }
...@@ -3639,6 +3639,15 @@ However, you aren’t invisible. Going private doesn’t hide your browsing from ...@@ -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."> <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 AR
</message> </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> </if>
<!-- Custom Context Menu Informations --> <!-- Custom Context Menu Informations -->
......
...@@ -12,10 +12,8 @@ ...@@ -12,10 +12,8 @@
android:minSdkVersion="24" android:minSdkVersion="24"
android:targetSdkVersion="{{target_sdk_version}}" /> android:targetSdkVersion="{{target_sdk_version}}" />
<!-- TODO(crbug.com/863068): Set dist:onDemand="true" once we can on-demand
install modules. -->
<dist:module <dist:module
dist:onDemand="false" dist:onDemand="true"
dist:title="@string/ar_module_title"> dist:title="@string/ar_module_title">
<dist:fusing dist:include="false" /> <dist:fusing dist:include="false" />
</dist:module> </dist:module>
......
...@@ -177,6 +177,9 @@ void ARCoreDevice::RequestArModule(int render_process_id, ...@@ -177,6 +177,9 @@ void ARCoreDevice::RequestArModule(int render_process_id,
int render_frame_id, int render_frame_id,
bool has_user_activation) { bool has_user_activation) {
if (arcore_java_utils_->ShouldRequestInstallArModule()) { 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(); arcore_java_utils_->RequestInstallArModule();
return; return;
} }
...@@ -235,6 +238,14 @@ void ARCoreDevice::RequestArCoreInstallOrUpdate(int render_process_id, ...@@ -235,6 +238,14 @@ void ARCoreDevice::RequestArCoreInstallOrUpdate(int render_process_id,
has_user_activation, true); 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() { void ARCoreDevice::OnRequestInstallSupportedARCoreCanceled() {
DCHECK(IsOnMainThread()); DCHECK(IsOnMainThread());
DCHECK(is_arcore_gl_thread_initialized_); DCHECK(is_arcore_gl_thread_initialized_);
......
...@@ -46,6 +46,7 @@ class ARCoreDevice : public VRDeviceBase { ...@@ -46,6 +46,7 @@ class ARCoreDevice : public VRDeviceBase {
return weak_ptr_factory_.GetWeakPtr(); return weak_ptr_factory_.GetWeakPtr();
} }
void OnRequestInstallArModuleResult(bool success);
void OnRequestInstallSupportedARCoreCanceled(); void OnRequestInstallSupportedARCoreCanceled();
private: private:
...@@ -136,6 +137,7 @@ class ARCoreDevice : public VRDeviceBase { ...@@ -136,6 +137,7 @@ class ARCoreDevice : public VRDeviceBase {
base::OnceCallback<void(bool)> base::OnceCallback<void(bool)>
on_request_arcore_install_or_update_result_callback_; on_request_arcore_install_or_update_result_callback_;
base::OnceCallback<void(bool)> on_request_ar_module_result_callback_;
// Must be last. // Must be last.
base::WeakPtrFactory<ARCoreDevice> weak_ptr_factory_; base::WeakPtrFactory<ARCoreDevice> weak_ptr_factory_;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "chrome/browser/android/vr/arcore_device/arcore_java_utils.h" #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_device.h"
#include "chrome/browser/android/vr/arcore_device/arcore_shim.h" #include "chrome/browser/android/vr/arcore_device/arcore_shim.h"
#include "jni/ArCoreJavaUtils_jni.h" #include "jni/ArCoreJavaUtils_jni.h"
...@@ -23,7 +24,11 @@ bool ArCoreJavaUtils::EnsureLoaded() { ...@@ -23,7 +24,11 @@ bool ArCoreJavaUtils::EnsureLoaded() {
if (!Java_ArCoreJavaUtils_shouldLoadArCoreSdk(env)) if (!Java_ArCoreJavaUtils_shouldLoadArCoreSdk(env))
return false; 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) ArCoreJavaUtils::ArCoreJavaUtils(device::ARCoreDevice* arcore_device)
...@@ -52,12 +57,13 @@ void ArCoreJavaUtils::OnRequestInstallSupportedArCoreCanceled( ...@@ -52,12 +57,13 @@ void ArCoreJavaUtils::OnRequestInstallSupportedArCoreCanceled(
} }
bool ArCoreJavaUtils::ShouldRequestInstallArModule() { bool ArCoreJavaUtils::ShouldRequestInstallArModule() {
// TODO(crbug.com/863068): Check whether AR module is already installed. return Java_ArCoreJavaUtils_shouldRequestInstallArModule(
return false; AttachCurrentThread(), j_arcore_java_utils_);
} }
void ArCoreJavaUtils::RequestInstallArModule() { void ArCoreJavaUtils::RequestInstallArModule() {
// TODO(crbug.com/863068): On-demand install AR module. Java_ArCoreJavaUtils_requestInstallArModule(AttachCurrentThread(),
j_arcore_java_utils_);
} }
bool ArCoreJavaUtils::ShouldRequestInstallSupportedArCore() { bool ArCoreJavaUtils::ShouldRequestInstallSupportedArCore() {
...@@ -74,4 +80,11 @@ void ArCoreJavaUtils::RequestInstallSupportedArCore( ...@@ -74,4 +80,11 @@ void ArCoreJavaUtils::RequestInstallSupportedArCore(
j_tab_android); j_tab_android);
} }
void ArCoreJavaUtils::OnRequestInstallArModuleResult(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
bool success) {
arcore_device_->OnRequestInstallArModuleResult(success);
}
} // namespace vr } // namespace vr
...@@ -27,7 +27,11 @@ class ArCoreJavaUtils { ...@@ -27,7 +27,11 @@ class ArCoreJavaUtils {
void RequestInstallSupportedArCore( void RequestInstallSupportedArCore(
base::android::ScopedJavaLocalRef<jobject> j_tab_android); 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( void OnRequestInstallSupportedArCoreCanceled(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj); const base::android::JavaParamRef<jobject>& obj);
......
...@@ -66,11 +66,11 @@ void LoadFunction(void* handle, const char* function_name, Fn* fn_out) { ...@@ -66,11 +66,11 @@ void LoadFunction(void* handle, const char* function_name, Fn* fn_out) {
namespace vr { namespace vr {
bool LoadArCoreSdk() { bool LoadArCoreSdk(const std::string& libraryPath) {
if (arcore_api) if (arcore_api)
return true; 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) { if (!sdk_handle) {
char* error_string = nullptr; char* error_string = nullptr;
error_string = dlerror(); error_string = dlerror();
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
namespace vr { namespace vr {
// TODO(vollick): add support for unloading the SDK. // TODO(vollick): add support for unloading the SDK.
bool LoadArCoreSdk(); bool LoadArCoreSdk(const std::string& libraryPath);
} // namespace vr } // 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 @@ ...@@ -66,6 +66,7 @@
"chrome/android/webapk/strings/android_webapk_strings.grd", "chrome/android/webapk/strings/android_webapk_strings.grd",
"components/autofill/android/java/strings/autofill_strings.grd", "components/autofill/android/java/strings/autofill_strings.grd",
"components/embedder_support/android/java/strings/web_contents_delegate_android_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", "content/public/android/java/strings/android_content_strings.grd",
"ui/android/java/strings/android_ui_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