| // 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.builder; |
| |
| import androidx.annotation.VisibleForTesting; |
| |
| import org.chromium.base.BundleUtils; |
| import org.chromium.base.StrictModeContext; |
| import org.chromium.base.annotations.JNINamespace; |
| import org.chromium.base.annotations.NativeMethods; |
| import org.chromium.base.library_loader.LibraryLoader; |
| import org.chromium.components.module_installer.engine.InstallEngine; |
| import org.chromium.components.module_installer.engine.InstallListener; |
| import org.chromium.components.module_installer.util.Timer; |
| |
| /** |
| * Represents a feature module. Can be used to install the module, access its interface, etc. See |
| * {@link ModuleInterface} for how to conveniently create an instance of the module class for a |
| * specific feature module. |
| * |
| * @param <T> The interface of the module |
| */ |
| @JNINamespace("module_installer") |
| public class Module<T> { |
| private final String mName; |
| private final Class<T> mInterfaceClass; |
| private final String mImplClassName; |
| private T mImpl; |
| private InstallEngine mInstaller; |
| private boolean mIsNativeLoaded; |
| |
| /** |
| * Instantiates a module. |
| * |
| * @param name The module's name as used with {@link ModuleInstaller}. |
| * @param interfaceClass {@link Class} object of the module interface. |
| * @param implClassName fully qualified class name of the implementation of the module's |
| * interface. |
| */ |
| public Module(String name, Class<T> interfaceClass, String implClassName) { |
| mName = name; |
| mInterfaceClass = interfaceClass; |
| mImplClassName = implClassName; |
| } |
| |
| @VisibleForTesting |
| public InstallEngine getInstallEngine() { |
| if (mInstaller == null) { |
| try (Timer timer = new Timer()) { |
| mInstaller = new ModuleEngine(mImplClassName); |
| } |
| } |
| return mInstaller; |
| } |
| |
| @VisibleForTesting |
| public void setInstallEngine(InstallEngine engine) { |
| mInstaller = engine; |
| } |
| |
| /** |
| * Returns true if the module is currently installed and can be accessed. |
| */ |
| public boolean isInstalled() { |
| try (Timer timer = new Timer()) { |
| return getInstallEngine().isInstalled(mName); |
| } |
| } |
| |
| /** |
| * Requests install of the module. |
| */ |
| public void install(InstallListener listener) { |
| try (Timer timer = new Timer()) { |
| assert !isInstalled(); |
| getInstallEngine().install(mName, listener); |
| } |
| } |
| |
| /** |
| * Requests deferred install of the module. |
| */ |
| public void installDeferred() { |
| try (Timer timer = new Timer()) { |
| getInstallEngine().installDeferred(mName); |
| } |
| } |
| |
| /** |
| * Returns the implementation of the module interface. Must only be called if the module is |
| * installed. |
| */ |
| public T getImpl() { |
| try (Timer timer = new Timer()) { |
| if (mImpl != null) return mImpl; |
| assert isInstalled(); |
| |
| ModuleDescriptor moduleDescriptor = loadModuleDescriptor(mName); |
| if (moduleDescriptor.getLoadNativeOnGetImpl()) { |
| // Load the module's native code and/or resources if they are present, and the |
| // Chrome native library itself has been loaded. |
| ensureNativeLoaded(); |
| } |
| |
| mImpl = mInterfaceClass.cast(instantiateReflectively(mImplClassName)); |
| return mImpl; |
| } |
| } |
| |
| /** |
| * Loads native libraries and/or resources if and only if this is not already done, assuming |
| * that the module is installed, and enableNativeLoad() has been called. |
| */ |
| public void ensureNativeLoaded() { |
| // Can only initialize native once per lifetime of Chrome. |
| if (mIsNativeLoaded) return; |
| assert LibraryLoader.getInstance().isInitialized(); |
| ModuleDescriptor moduleDescriptor = loadModuleDescriptor(mName); |
| String[] libraries = moduleDescriptor.getLibraries(); |
| String[] paks = moduleDescriptor.getPaks(); |
| if (libraries.length > 0 || paks.length > 0) { |
| ModuleJni.get().loadNative(mName, libraries, paks); |
| } |
| mIsNativeLoaded = true; |
| } |
| |
| /** |
| * Loads the {@link ModuleDescriptor} for a module. |
| * |
| * For bundles, uses reflection to load the descriptor from inside the |
| * module. For APKs, returns an empty descriptor since APKs won't have |
| * descriptors packaged into them. |
| * |
| * @param name The module's name. |
| * @return The module's {@link ModuleDescriptor}. |
| */ |
| private static ModuleDescriptor loadModuleDescriptor(String name) { |
| if (!BundleUtils.isBundle()) { |
| return new ModuleDescriptor() { |
| @Override |
| public String[] getLibraries() { |
| return new String[0]; |
| } |
| |
| @Override |
| public String[] getPaks() { |
| return new String[0]; |
| } |
| |
| @Override |
| public boolean getLoadNativeOnGetImpl() { |
| return false; |
| } |
| }; |
| } |
| |
| return (ModuleDescriptor) instantiateReflectively( |
| "org.chromium.components.module_installer.builder.ModuleDescriptor_" + name); |
| } |
| |
| /** |
| * Instantiates an object via reflection. |
| * |
| * Ignores strict mode violations since accessing code in a module may cause its DEX file to be |
| * loaded and on some devices that can cause such a violation. |
| * |
| * @param className The object's class name. |
| * @return The object. |
| */ |
| private static Object instantiateReflectively(String className) { |
| try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) { |
| return Class.forName(className).newInstance(); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @NativeMethods |
| interface Natives { |
| void loadNative(String name, String[] libraries, String[] paks); |
| } |
| } |