Commit 7db51ac3 authored by Peter Wen's avatar Peter Wen Committed by Commit Bot

Android: Mock ModuleInstaller in tests

- Split ModuleInstaller to ModuleInstallerImpl and its interface.
- Remove //components/module_installer/../src-test
- Create a new ModuleInstallerRule to use in tests.
- Update bytecode rewriting to use getInstance().
- Remove dependency on arcore in chrome_test_java (used on Kitkat).

Bug: 981084
Change-Id: I7909fe67fa29bdb0e5c787faf4293600283421ea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1687303
Commit-Queue: Peter Wen <wnwen@chromium.org>
Reviewed-by: default avatarEric Stevenson <estevenson@chromium.org>
Reviewed-by: default avatarTibor Goldschwendt <tiborg@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Auto-Submit: Peter Wen <wnwen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#676623}
parent 246f74b8
......@@ -113,15 +113,20 @@ class ByteCodeProcessor {
writer = new ClassWriter(reader, 0);
}
ClassVisitor chain = writer;
/* DEBUGGING:
To see the bytecode for a specific class:
if (entry.getName().contains("YourClassName")) {
chain = new TraceClassVisitor(chain, new PrintWriter(System.out));
}
/* DEBUGGING:}
To see objectweb.asm code that will generate bytecode for a given class:
java -cp "third_party/ow2_asm/lib/asm-5.0.1.jar:third_party/ow2_asm/lib/"\
"asm-util-5.0.1.jar:out/Debug/lib.java/jar_containing_yourclass.jar" \
org.objectweb.asm.util.ASMifier org.package.YourClassName
java -cp
"third_party/ow2_asm/lib/asm.jar:third_party/ow2_asm/lib/asm-util.jar:out/Debug/lib.java/jar_containing_yourclass.jar"
org.objectweb.asm.util.ASMifier org.package.YourClassName
See this pdf for more details: https://asm.ow2.io/asm4-guide.pdf
To see the bytecode for a specific class, uncomment this code with your class name:
if (entry.getName().contains("YOUR_CLASS_NAME")) {
chain = new TraceClassVisitor(chain, new PrintWriter(System.out));
}
*/
if (sShouldUseThreadAnnotations) {
chain = new ThreadAssertionClassAdapter(chain);
......
......@@ -6,6 +6,7 @@ package org.chromium.bytecode;
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.RETURN;
......@@ -34,6 +35,9 @@ class SplitCompatClassAdapter extends ClassVisitor {
private static final String MODULE_INSTALLER_CLASS_NAME =
"org/chromium/components/module_installer/ModuleInstaller";
private static final String GET_INSTANCE_METHOD_NAME = "getInstance";
private static final String GET_INSTANCE_DESCRIPTOR =
TypeUtils.getMethodDescriptor(MODULE_INSTALLER_CLASS_NAME);
private static final String INIT_ACTIVITY_METHOD_NAME = "initActivity";
private static final String INIT_ACTIVITY_DESCRIPTOR =
TypeUtils.getMethodDescriptor(VOID, CONTEXT);
......@@ -107,7 +111,7 @@ class SplitCompatClassAdapter extends ClassVisitor {
* <pre>
* protected void attachBaseContext(Context base) {
* super.attachBaseContext(base);
* ModuleInstaller.initActivity(this);
* ModuleInstaller.getInstance().initActivity(this);
* }
* </pre>
*/
......@@ -115,16 +119,30 @@ class SplitCompatClassAdapter extends ClassVisitor {
MethodVisitor mv = super.visitMethod(ACC_PROTECTED, ATTACH_BASE_CONTEXT_METHOD_NAME,
ATTACH_BASE_CONTEXT_DESCRIPTOR, null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0); // load "this" on stack
mv.visitVarInsn(ALOAD, 1); // load first method parameter on stack (Context)
// Push "this" on stack.
mv.visitVarInsn(ALOAD, 0);
// Push first method parameter on stack (Context).
mv.visitVarInsn(ALOAD, 1);
// Pop argument from stack (Context).
// Pop target object from stack ("this").
// Calls attachBaseContext.
mv.visitMethodInsn(INVOKESPECIAL, ANDROID_APP_ACTIVITY_CLASS_NAME,
ATTACH_BASE_CONTEXT_METHOD_NAME,
ATTACH_BASE_CONTEXT_DESCRIPTOR); // invoke super's attach base context
mv.visitVarInsn(ALOAD, 0); // load "this" on stack
mv.visitMethodInsn(INVOKESTATIC, MODULE_INSTALLER_CLASS_NAME, INIT_ACTIVITY_METHOD_NAME,
INIT_ACTIVITY_DESCRIPTOR);
ATTACH_BASE_CONTEXT_METHOD_NAME, ATTACH_BASE_CONTEXT_DESCRIPTOR, false);
// Push return value on stack (ModuleInstaller).
// Calls getInstance.
mv.visitMethodInsn(INVOKESTATIC, MODULE_INSTALLER_CLASS_NAME, GET_INSTANCE_METHOD_NAME,
GET_INSTANCE_DESCRIPTOR, false);
// Push "this" on stack.
mv.visitVarInsn(ALOAD, 0);
// Pop argument from stack ("this").
// Pop target object from stack (ModuleInstaller).
// Calls initActivity.
mv.visitMethodInsn(INVOKEINTERFACE, MODULE_INSTALLER_CLASS_NAME, INIT_ACTIVITY_METHOD_NAME,
INIT_ACTIVITY_DESCRIPTOR, true);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2); // max stack size - 2, max locals - 2
// Max stack size = 2 (Only push at most 2 before popping).
// Max locals = 2 ("this" and 1 parameter).
mv.visitMaxs(2, 2);
mv.visitEnd();
}
......
......@@ -852,7 +852,6 @@ android_library("chrome_test_java") {
"//third_party/android_deps:com_android_support_preference_v7_java",
"//third_party/android_deps:com_android_support_recyclerview_v7_java",
"//third_party/android_deps:com_android_support_support_annotations_java",
"//third_party/android_deps:com_google_ar_core_java",
"//third_party/android_deps:com_google_protobuf_protobuf_lite_java",
"//third_party/android_sdk:android_test_base_java",
"//third_party/android_sdk:android_test_mock_java",
......@@ -1039,6 +1038,7 @@ if (enable_vr || enable_arcore) {
"//third_party/gvr-android-sdk:gvr_common_java",
":chrome_test_util_java",
"//components/module_installer/android:module_installer_java",
"//components/module_installer/android:module_installer_stub_java",
"//components/module_installer/android:module_installer_test_java",
]
......
......@@ -226,9 +226,6 @@ template("chrome_public_common_apk_or_module_tmpl") {
if (_target_type == "android_app_bundle_module") {
deps +=
[ "//components/module_installer/android:module_installer_impl_java" ]
} else if (_target_type == "instrumentation_test_apk") {
deps +=
[ "//components/module_installer/android:module_installer_test_java" ]
} else {
deps +=
[ "//components/module_installer/android:module_installer_stub_java" ]
......
......@@ -27,7 +27,7 @@ public class AutofillAssistantModuleEntryProvider {
@Nullable
/* package */ static AutofillAssistantModuleEntry getModuleEntryIfInstalled(Context context) {
// Required to access resources in DFM using this activity as context.
ModuleInstaller.initActivity(context);
ModuleInstaller.getInstance().initActivity(context);
if (AutofillAssistantModule.isInstalled()) {
return AutofillAssistantModule.getImpl();
}
......@@ -92,11 +92,11 @@ public class AutofillAssistantModuleEntryProvider {
});
// Shows toast informing user about install start.
ui.showInstallStartUi();
ModuleInstaller.install("autofill_assistant", (success) -> {
ModuleInstaller.getInstance().install("autofill_assistant", (success) -> {
if (success) {
// Clean install of chrome will have issues here without initializing
// after installation of DFM.
ModuleInstaller.initActivity(activity);
ModuleInstaller.getInstance().initActivity(activity);
// Don't show success UI from DFM, transition to autobot UI directly.
callback.onResult(AutofillAssistantModule.getImpl());
return;
......
......@@ -121,12 +121,12 @@ public class ChromeApplication extends Application {
// Record via UMA all modules that have been requested and are currently installed. This
// will tell us the install penetration of each module over time.
ModuleInstaller.recordModuleAvailability();
ModuleInstaller.getInstance().recordModuleAvailability();
}
// Write installed modules to crash keys. This needs to be done as early as possible so that
// these values are set before any crashes are reported.
ModuleInstaller.updateCrashKeys();
ModuleInstaller.getInstance().updateCrashKeys();
BuildInfo.setFirebaseAppId(FirebaseConfig.getFirebaseAppId());
......
......@@ -60,7 +60,7 @@ public class ArCoreInstallUtils implements ModuleInstallUi.FailureUiListener {
private ArCoreInstallUtils(long nativeArCoreInstallUtils) {
mNativeArCoreInstallUtils = nativeArCoreInstallUtils;
// Need to be called before trying to access the AR module.
ModuleInstaller.init();
ModuleInstaller.getInstance().init();
}
@Override
......@@ -98,7 +98,7 @@ public class ArCoreInstallUtils implements ModuleInstallUi.FailureUiListener {
mTab = tab;
ModuleInstallUi ui = new ModuleInstallUi(mTab, R.string.ar_module_title, this);
ui.showInstallStartUi();
ModuleInstaller.install("ar", success -> {
ModuleInstaller.getInstance().install("ar", success -> {
assert shouldRequestInstallArModule() != success;
if (success) {
......
......@@ -29,12 +29,17 @@ import org.chromium.chrome.browser.vr.util.VrTestRuleUtils;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.components.module_installer.ModuleInstaller;
import org.chromium.components.module_installer.ModuleInstallerRule;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
/**
* End-to-end tests for installing the VR DFM on Daydream-ready phones on startup.
*
* TODO(agrieve): This test may be better as a robolectric test.
*/
@RunWith(ParameterizedRunner.class)
@UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
......@@ -48,12 +53,24 @@ public class VrDaydreamReadyModuleInstallTest {
@Rule
public RuleChain mRuleChain;
private ModuleInstallerRule mModuleInstallerRule;
private ChromeActivityTestRule mVrTestRule;
private final Set<String> mModulesRequestedDeferred = new HashSet<>();
public VrDaydreamReadyModuleInstallTest(Callable<ChromeActivityTestRule> callable)
throws Exception {
mVrTestRule = callable.call();
mRuleChain = VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mVrTestRule);
mModuleInstallerRule = new ModuleInstallerRule(new ModuleInstaller() {
@Override
public void installDeferred(String moduleName) {
mModulesRequestedDeferred.add(moduleName);
}
});
mRuleChain =
RuleChain.outerRule(mModuleInstallerRule)
.around(VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mVrTestRule));
}
/** Tests that the install is requested deferred. */
......@@ -62,8 +79,8 @@ public class VrDaydreamReadyModuleInstallTest {
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
@Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM})
@VrModuleNotInstalled
public void testDeferredRequestOnStartup() throws InterruptedException {
public void testDeferredRequestOnStartup() {
Assert.assertTrue("VR module should have been deferred installed at startup",
ModuleInstaller.didRequestDeferred("vr"));
mModulesRequestedDeferred.contains("vr"));
}
}
......@@ -10,11 +10,12 @@ import("//build/config/android/rules.gni")
# build.
android_library("module_installer_java") {
java_files = [
"java/src-stub/org/chromium/components/module_installer/ModuleInstaller.java",
"java/src-stub/org/chromium/components/module_installer/ModuleInstallerImpl.java",
"java/src-common/org/chromium/components/module_installer/ModuleInstaller.java",
"java/src-common/org/chromium/components/module_installer/OnModuleInstallFinishedListener.java",
"java/src-common/org/chromium/components/module_installer/Module.java",
]
jar_excluded_patterns = [ "*/ModuleInstaller.class" ]
jar_excluded_patterns = [ "*/ModuleInstallerImpl.class" ]
deps = [
"//base:base_java",
]
......@@ -24,7 +25,7 @@ android_library("module_installer_java") {
# Contains stub implementation to be used for builds not supporting modules
# (e.g. APKs).
android_library("module_installer_stub_java") {
java_files = [ "java/src-stub/org/chromium/components/module_installer/ModuleInstaller.java" ]
java_files = [ "java/src-stub/org/chromium/components/module_installer/ModuleInstallerImpl.java" ]
deps = [
":module_installer_java",
"//base:base_java",
......@@ -36,7 +37,7 @@ android_library("module_installer_stub_java") {
# bundles).
android_library("module_installer_impl_java") {
java_files = [
"java/src-impl/org/chromium/components/module_installer/ModuleInstaller.java",
"java/src-impl/org/chromium/components/module_installer/ModuleInstallerImpl.java",
"java/src-impl/org/chromium/components/module_installer/ModuleInstallerBackend.java",
"java/src-impl/org/chromium/components/module_installer/FakeModuleInstallerBackend.java",
"java/src-impl/org/chromium/components/module_installer/PlayCoreModuleInstallerBackend.java",
......@@ -50,10 +51,12 @@ android_library("module_installer_impl_java") {
}
android_library("module_installer_test_java") {
java_files = [ "java/src-test/org/chromium/components/module_installer/ModuleInstaller.java" ]
testonly = true
java_files = [ "javatests/src/org/chromium/components/module_installer/ModuleInstallerRule.java" ]
deps = [
":module_installer_java",
"//base:base_java",
"//third_party/junit",
]
jacoco_never_instrument = true
}
......
......@@ -56,7 +56,7 @@ public class Module<T> {
// Accessing classes in the module may cause its DEX file to be loaded. And on some devices
// that causes a read mode violation.
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
ModuleInstaller.init();
ModuleInstaller.getInstance().init();
Class.forName(mImplClassName);
return true;
} catch (ClassNotFoundException e) {
......@@ -64,18 +64,18 @@ public class Module<T> {
}
}
/** Requests install of the module. See {@link ModuleInstaller#install} for more details. */
/** Requests install of the module. See {@link ModuleInstallerImpl#install} for more details. */
public void install(OnModuleInstallFinishedListener onFinishedListener) {
assert !isInstalled();
ModuleInstaller.install(mName, onFinishedListener);
ModuleInstaller.getInstance().install(mName, onFinishedListener);
}
/**
* Requests deferred install of the module. See {@link ModuleInstaller#installDeferred} for
* Requests deferred install of the module. See {@link ModuleInstallerImpl#installDeferred} for
* more details.
*/
public void installDeferred() {
ModuleInstaller.installDeferred(mName);
ModuleInstaller.getInstance().installDeferred(mName);
}
/**
......@@ -85,7 +85,7 @@ public class Module<T> {
public T getImpl() {
assert isInstalled();
if (mImpl == null) {
ModuleInstaller.init();
ModuleInstaller.getInstance().init();
// Accessing classes in the module may cause its DEX file to be loaded. And on some
// devices that causes a read mode violation.
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
......
// Copyright 2019 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 android.content.Context;
import org.chromium.base.VisibleForTesting;
/**
* This interface contains all the necessary methods to orchestrate the installation of dynamic
* feature modules (DFMs).
*/
public interface ModuleInstaller {
/** Returns the singleton instance from the correct implementation. */
static ModuleInstaller getInstance() {
return ModuleInstallerImpl.getInstance();
}
@VisibleForTesting
static void setInstanceForTesting(ModuleInstaller moduleInstaller) {
ModuleInstallerImpl.setInstanceForTesting(moduleInstaller);
}
/** Needs to be called before trying to access a module. */
default void init() {}
/**
* Needs to be called in attachBaseContext of the activities that want to have access to
* splits prior to application restart.
*
* For details, see:
* https://developer.android.com/reference/com/google/android/play/core/splitcompat/SplitCompat.html#install(android.content.Context)
*/
default void initActivity(Context context) {}
/**
* Records via UMA all modules that have been requested and are currently installed. The intent
* is to measure the install penetration of each module.
*/
default void recordModuleAvailability() {}
/** Writes fully installed and emulated modules to crash keys. */
default void updateCrashKeys() {}
/**
* 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.
*/
default void install(String moduleName, OnModuleInstallFinishedListener onFinishedListener) {}
/**
* Asynchronously installs module in the background when on unmetered connection and charging.
* Install is best effort and may fail silently. Upon success, the module will only be available
* after Chrome restarts.
*
* @param moduleName Name of the module.
*/
default void installDeferred(String moduleName) {}
}
......@@ -30,53 +30,54 @@ import java.util.Set;
import java.util.TreeSet;
/** Installs dynamic feature modules (DFMs). */
public class ModuleInstaller {
public class ModuleInstallerImpl implements ModuleInstaller {
/** Command line switch for activating the fake backend. */
private static final String FAKE_FEATURE_MODULE_INSTALL = "fake-feature-module-install";
private static final Map<String, List<OnModuleInstallFinishedListener>> sModuleNameListenerMap =
private static ModuleInstaller sInstance = new ModuleInstallerImpl();
private static boolean sAppContextSplitCompatted;
private final Map<String, List<OnModuleInstallFinishedListener>> mModuleNameListenerMap =
new HashMap<>();
private static ModuleInstallerBackend sBackend;
private static boolean sSplitCompatted;
private ModuleInstallerBackend mBackend;
/** Needs to be called before trying to access a module. */
public static void init() {
if (sSplitCompatted) return;
/** Returns the singleton instance. */
public static ModuleInstaller getInstance() {
return sInstance;
}
public static void setInstanceForTesting(ModuleInstaller moduleInstaller) {
sInstance = moduleInstaller;
}
@Override
public void init() {
if (sAppContextSplitCompatted) return;
// SplitCompat.install may copy modules into Chrome's internal folder or clean them up.
try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
SplitCompat.install(ContextUtils.getApplicationContext());
sSplitCompatted = true;
sAppContextSplitCompatted = true;
}
// SplitCompat.install may add emulated modules. Thus, update crash keys.
updateCrashKeys();
}
/**
* Needs to be called in attachBaseContext of the activities that want to have access to
* splits prior to application restart.
*
* For details, see:
* https://developer.android.com/reference/com/google/android/play/core/splitcompat/SplitCompat.html#install(android.content.Context)
*/
public static void initActivity(Context context) {
@Override
public void initActivity(Context context) {
SplitCompat.install(context);
}
/**
* Records via UMA all modules that have been requested and are currently installed. The intent
* is to measure the install penetration of each module.
*/
public static void recordModuleAvailability() {
@Override
public void recordModuleAvailability() {
if (!CommandLine.getInstance().hasSwitch(FAKE_FEATURE_MODULE_INSTALL)) {
PlayCoreModuleInstallerBackend.recordModuleAvailability();
}
}
/** Writes fully installed and emulated modules to crash keys. */
public static void updateCrashKeys() {
@Override
public void updateCrashKeys() {
Context context = ContextUtils.getApplicationContext();
// Get modules that are fully installed as split APKs (excluding base which is always
// intalled). Tree set to have ordered and, thus, deterministic results.
// installed). Tree set to have ordered and, thus, deterministic results.
Set<String> fullyInstalledModules = new TreeSet<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Split APKs are only supported on Android L+.
......@@ -97,7 +98,7 @@ public class ModuleInstaller {
// emulation of later modules won't work. If splitcompat has not been called no modules are
// emulated. Therefore, use an empty set in that case.
Set<String> emulatedModules = new TreeSet<>();
if (sSplitCompatted) {
if (sAppContextSplitCompatted) {
emulatedModules.addAll(
SplitInstallManagerFactory.create(context).getInstalledModules());
emulatedModules.removeAll(fullyInstalledModules);
......@@ -109,21 +110,15 @@ public class ModuleInstaller {
CrashKeyIndex.EMULATED_MODULES, encodeCrashKeyValue(emulatedModules));
}
/**
* 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, OnModuleInstallFinishedListener onFinishedListener) {
@Override
public void install(String moduleName, OnModuleInstallFinishedListener onFinishedListener) {
ThreadUtils.assertOnUiThread();
if (!sModuleNameListenerMap.containsKey(moduleName)) {
sModuleNameListenerMap.put(moduleName, new LinkedList<>());
if (!mModuleNameListenerMap.containsKey(moduleName)) {
mModuleNameListenerMap.put(moduleName, new LinkedList<>());
}
List<OnModuleInstallFinishedListener> onFinishedListeners =
sModuleNameListenerMap.get(moduleName);
mModuleNameListenerMap.get(moduleName);
onFinishedListeners.add(onFinishedListener);
if (onFinishedListeners.size() > 1) {
// Request is already running.
......@@ -132,56 +127,50 @@ public class ModuleInstaller {
getBackend().install(moduleName);
}
/**
* Asynchronously installs module in the background when on unmetered connection and charging.
* Install is best effort and may fail silently. Upon success, the module will only be available
* after Chrome restarts.
*
* @param moduleName Name of the module.
*/
public static void installDeferred(String moduleName) {
@Override
public void installDeferred(String moduleName) {
ThreadUtils.assertOnUiThread();
getBackend().installDeferred(moduleName);
}
private static void onFinished(boolean success, List<String> moduleNames) {
private void onFinished(boolean success, List<String> moduleNames) {
ThreadUtils.assertOnUiThread();
for (String moduleName : moduleNames) {
List<OnModuleInstallFinishedListener> onFinishedListeners =
sModuleNameListenerMap.get(moduleName);
mModuleNameListenerMap.get(moduleName);
if (onFinishedListeners == null) continue;
for (OnModuleInstallFinishedListener listener : onFinishedListeners) {
listener.onFinished(success);
}
sModuleNameListenerMap.remove(moduleName);
mModuleNameListenerMap.remove(moduleName);
}
if (sModuleNameListenerMap.isEmpty()) {
sBackend.close();
sBackend = null;
if (mModuleNameListenerMap.isEmpty()) {
mBackend.close();
mBackend = null;
}
updateCrashKeys();
}
private static ModuleInstallerBackend getBackend() {
if (sBackend == null) {
ModuleInstallerBackend.OnFinishedListener listener = ModuleInstaller::onFinished;
sBackend = CommandLine.getInstance().hasSwitch(FAKE_FEATURE_MODULE_INSTALL)
private ModuleInstallerBackend getBackend() {
if (mBackend == null) {
ModuleInstallerBackend.OnFinishedListener listener = this::onFinished;
mBackend = CommandLine.getInstance().hasSwitch(FAKE_FEATURE_MODULE_INSTALL)
? new FakeModuleInstallerBackend(listener)
: new PlayCoreModuleInstallerBackend(listener);
}
return sBackend;
return mBackend;
}
private static String encodeCrashKeyValue(Set<String> moduleNames) {
private String encodeCrashKeyValue(Set<String> moduleNames) {
if (moduleNames.isEmpty()) return "<none>";
// Values with dots are interpreted as URLs. Some module names have dots in them. Make sure
// they don't get sanitized.
return TextUtils.join(",", moduleNames).replace('.', '$');
}
private ModuleInstaller() {}
private ModuleInstallerImpl() {}
}
......@@ -20,7 +20,6 @@ import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.components.module_installer.ModuleInstallerBackend.OnFinishedListener;
import java.util.Collections;
import java.util.HashMap;
......@@ -93,7 +92,7 @@ import java.util.Set;
/** Records via UMA all modules that have been requested and are currently installed. */
public static void recordModuleAvailability() {
// MUST call init before creating a SplitInstallManager.
ModuleInstaller.init();
ModuleInstaller.getInstance().init();
SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
Set<String> requestedModules = new HashSet<>();
requestedModules.addAll(
......@@ -128,7 +127,7 @@ import java.util.Set;
public PlayCoreModuleInstallerBackend(OnFinishedListener listener) {
super(listener);
// MUST call init before creating a SplitInstallManager.
ModuleInstaller.init();
ModuleInstaller.getInstance().init();
mManager = SplitInstallManagerFactory.create(ContextUtils.getApplicationContext());
mManager.registerListener(this);
}
......
......@@ -4,33 +4,29 @@
package org.chromium.components.module_installer;
import android.content.Context;
import org.chromium.base.VisibleForTesting;
/** Dummy fallback of ModuleInstaller for APK builds. */
public class ModuleInstaller {
public static void init() {}
public class ModuleInstallerImpl implements ModuleInstaller {
/** A valid singleton instance is necessary for tests to swap it out. */
private static ModuleInstaller sInstance = new ModuleInstallerImpl();
public static void initActivity(Context context) {}
public static void recordModuleAvailability() {}
/** Returns the singleton instance. */
public static ModuleInstaller getInstance() {
return sInstance;
}
public static void updateCrashKeys(){};
public static void setInstanceForTesting(ModuleInstaller moduleInstaller) {
sInstance = moduleInstaller;
}
public static void install(
String moduleName, OnModuleInstallFinishedListener onFinishedListener) {
@Override
public void install(String moduleName, OnModuleInstallFinishedListener onFinishedListener) {
throw new UnsupportedOperationException("Cannot install module if APK");
}
public static void installDeferred(String moduleName) {
@Override
public void installDeferred(String moduleName) {
throw new UnsupportedOperationException("Cannot deferred install module if APK");
}
@VisibleForTesting
public static boolean didRequestDeferred(String moduleName) {
throw new UnsupportedOperationException();
}
private ModuleInstaller() {}
private ModuleInstallerImpl() {}
}
// Copyright 2019 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 android.content.Context;
import org.chromium.base.VisibleForTesting;
import java.util.HashSet;
import java.util.Set;
/** Mock ModuleInstaller for use in tests. */
public class ModuleInstaller {
private static Set<String> sModulesRequestedDeffered = new HashSet<>();
public static void init() {}
public static void initActivity(Context context) {}
public static void recordModuleAvailability() {}
public static void updateCrashKeys(){};
public static void install(
String moduleName, OnModuleInstallFinishedListener onFinishedListener) {}
public static void installDeferred(String moduleName) {
sModulesRequestedDeffered.add(moduleName);
}
@VisibleForTesting
public static boolean didRequestDeferred(String moduleName) {
return sModulesRequestedDeffered.contains(moduleName);
}
private ModuleInstaller() {}
}
// Copyright 2019 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 org.junit.rules.ExternalResource;
/**
* This rule allows the caller to specify their own {@link ModuleInstaller} for the duration of the
* test and resets it back to what it was before.
*
* TODO(wnwen): This should eventually become ModuleConfigRule.
*/
public class ModuleInstallerRule extends ExternalResource {
private ModuleInstaller mOldModuleInstaller;
private final ModuleInstaller mMockModuleInstaller;
public ModuleInstallerRule(ModuleInstaller mockModuleInstaller) {
mMockModuleInstaller = mockModuleInstaller;
}
@Override
protected void before() {
mOldModuleInstaller = ModuleInstaller.getInstance();
ModuleInstaller.setInstanceForTesting(mMockModuleInstaller);
}
@Override
protected void after() {
ModuleInstaller.setInstanceForTesting(mOldModuleInstaller);
}
}
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