|  | # App Bundles and Dynamic Feature Modules (DFMs) | 
|  |  | 
|  | [TOC] | 
|  |  | 
|  | ## About Bundles | 
|  | [Android App bundles] is a Play Store feature that allows packaging an app as | 
|  | multiple `.apk` files, known as "splits". Bundles are zip files with an `.aab` | 
|  | extension. See [android_build_instructions.md#multiple-chrome-targets] for a | 
|  | list of buildable bundle targets. | 
|  |  | 
|  | Bundles provide three main advantages over monolithic `.apk` files: | 
|  | 1. Language resources are split into language-specific `.apk` files, known as | 
|  | "resource splits". Delivering only the active languages reduces the overhead | 
|  | of UI strings. | 
|  | * Resource splits can also be made on a per-screen-density basis (for drawables), | 
|  | but Chrome has not taken advantage of this (yet). | 
|  | 2. Features can be packaged into lazily loaded `.apk` files, known as | 
|  | "feature splits". Chrome enables [isolated splits], which means feature | 
|  | splits have no performance overhead until used (on Android O+ at least). | 
|  | 3. Feature splits can be downloaded on-demand, saving disk space for users that | 
|  | do not need the functionality they provide. These are known as | 
|  | "Dynamic feature modules", or "DFMs". | 
|  | * **The install experience for DFMs is quite poor (5-30 seconds install times, | 
|  | sometimes fails, sometimes [triggers a crash]).** | 
|  |  | 
|  | You can inspect which `.apk` files are produced by a bundle target via: | 
|  | ``` | 
|  | out/Default/bin/${target_name} build-bundle-apks --output-apks foo.apks | 
|  | unzip -l foo.apks | 
|  | ``` | 
|  |  | 
|  | *** note | 
|  | Adding new features via feature splits is highly encouraged when it makes sense | 
|  | to do so: | 
|  | * Has a non-trivial amount of Java code (after optimization). E.g. >150kb | 
|  | * Not needed on startup | 
|  | * Has a small integration surface (calls into it must be done with reflection) | 
|  | * Not used by WebView | 
|  | *** | 
|  |  | 
|  | [android_build_instructions.md#multiple-chrome-targets]: android_build_instructions.md#multiple-chrome-targets | 
|  | [Android App Bundles]: https://developer.android.com/guide/app-bundle | 
|  | [isolated splits]: android_isolated_splits.md | 
|  | [triggers a crash]: https://chromium.googlesource.com/chromium/src/+/main/docs/android_isolated_splits.md#Conflicting-ClassLoaders-2 | 
|  |  | 
|  | ### Declaring App Bundles with GN Templates | 
|  |  | 
|  | Here's an example that shows how to declare a simple bundle that contains a | 
|  | single base module, which enables language-based splits: | 
|  |  | 
|  | ```gn | 
|  | android_app_bundle_module("foo_base_module") { | 
|  | # Declaration are similar to android_apk here. | 
|  | ... | 
|  | } | 
|  |  | 
|  | android_app_bundle("foo_bundle") { | 
|  | base_module_target = ":foo_base_module" | 
|  |  | 
|  | # The name of our bundle file (without any suffix). | 
|  | bundle_name = "FooBundle" | 
|  |  | 
|  | # Enable language-based splits for this bundle. Which means that | 
|  | # resources and assets specific to a given language will be placed | 
|  | # into their own split APK in the final .apks archive. | 
|  | enable_language_splits = true | 
|  |  | 
|  | # Proguard settings must be passed at the bundle, not module, target. | 
|  | proguard_enabled = !is_java_debug | 
|  | } | 
|  | ``` | 
|  |  | 
|  | When generating the `foo_bundle` target with Ninja, you will end up with | 
|  | the following: | 
|  |  | 
|  | * The bundle file under `out/Release/apks/FooBundle.aab` | 
|  |  | 
|  | * A helper script called `out/Release/bin/foo_bundle`, which can be used | 
|  | to install / launch / uninstall the bundle on local devices. | 
|  |  | 
|  | This works like an APK wrapper script (e.g. `foo_apk`). Use `--help` | 
|  | to see all possible commands supported by the script. | 
|  |  | 
|  |  | 
|  | The remainder of this doc focuses on DFMs. | 
|  |  | 
|  | ## Declaring Dynamic Feature Modules (DFMs) | 
|  |  | 
|  | This guide walks you through the steps to create a DFM called _Foo_ and add it | 
|  | to the Chrome bundles. | 
|  |  | 
|  | *** note | 
|  | **Note:** To make your own module you'll essentially have to replace every | 
|  | instance of `foo`/`Foo`/`FOO` with `your_feature_name`/`YourFeatureName`/ | 
|  | `YOUR_FEATURE_NAME`. | 
|  | *** | 
|  |  | 
|  | ### Reference DFM | 
|  |  | 
|  | In addition to this guide, the | 
|  | [Test Dummy](https://cs.chromium.org/chromium/src/chrome/android/modules/test_dummy/test_dummy_module.gni) | 
|  | module serves as an actively-maintained reference DFM. Test Dummy is used in | 
|  | automated bundle testing, and covers both Java and native code and resource | 
|  | usage. | 
|  |  | 
|  | ### Create DFM target | 
|  |  | 
|  | DFMs are APKs. They have a manifest and can contain Java and native code as well | 
|  | as resources. This section walks you through creating the module target in our | 
|  | build system. | 
|  |  | 
|  | First, create the file | 
|  | `//chrome/android/modules/foo/internal/java/AndroidManifest.xml` and add: | 
|  |  | 
|  | ```xml | 
|  | <?xml version="1.0" encoding="utf-8"?> | 
|  | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | 
|  | xmlns:dist="http://schemas.android.com/apk/distribution" | 
|  | featureSplit="foo"> | 
|  |  | 
|  | <!-- dist:onDemand="true" makes this a separately installed module. | 
|  | dist:onDemand="false" would always install the module alongside the | 
|  | rest of Chrome. --> | 
|  | <dist:module | 
|  | dist:onDemand="true" | 
|  | dist:title="@string/foo_module_title"> | 
|  | <!-- This will fuse the module into the base APK if a system image | 
|  | APK is built from this bundle. --> | 
|  | <dist:fusing dist:include="true" /> | 
|  | </dist:module> | 
|  |  | 
|  | <!-- Remove android:hasCode="false" when adding Java code. --> | 
|  | <application android:hasCode="false" /> | 
|  | </manifest> | 
|  | ``` | 
|  |  | 
|  | Next, create a descriptor configuring the Foo module. To do this, create | 
|  | `//chrome/android/modules/foo/foo_module.gni` and add the following: | 
|  |  | 
|  | ```gn | 
|  | foo_module_desc = { | 
|  | name = "foo" | 
|  | android_manifest = | 
|  | "//chrome/android/modules/foo/internal/java/AndroidManifest.xml" | 
|  | } | 
|  | ``` | 
|  |  | 
|  | Then, add the module descriptor to the appropriate descriptor list in | 
|  | //chrome/android/modules/chrome_feature_modules.gni, e.g. the Chrome list: | 
|  |  | 
|  | ```gn | 
|  | import("//chrome/android/modules/foo/foo_module.gni") | 
|  | ... | 
|  | chrome_module_descs += [ foo_module_desc ] | 
|  | ``` | 
|  |  | 
|  | The next step is to add Foo to the list of feature modules for UMA recording. | 
|  | For this, add `foo` to the `AndroidFeatureModuleName` in | 
|  | `//tools/metrics/histograms/metadata/histogram_suffixes_list.xml`: | 
|  |  | 
|  | ```xml | 
|  | <histogram_suffixes name="AndroidFeatureModuleName" ...> | 
|  | ... | 
|  | <suffix name="foo" label="Super Duper Foo Module" /> | 
|  | ... | 
|  | </histogram_suffixes> | 
|  | ``` | 
|  |  | 
|  | Lastly, give your module a title that Chrome and Play can use for the install | 
|  | UI. To do this, add a string to | 
|  | `//chrome/browser/ui/android/strings/android_chrome_strings.grd`: | 
|  |  | 
|  | ```xml | 
|  | ... | 
|  | <message name="IDS_FOO_MODULE_TITLE" | 
|  | desc="Text shown when the Foo module is referenced in install start, success, | 
|  | failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to | 
|  | 'Installing Foo for Chrome…')."> | 
|  | Foo | 
|  | </message> | 
|  | ... | 
|  | ``` | 
|  |  | 
|  | *** note | 
|  | **Note:** This is for module title only. Other strings specific to the module | 
|  | should go in the module, not here (in the base module). | 
|  | *** | 
|  |  | 
|  | Congrats! You added the DFM Foo to Chrome. That is a big step but not very | 
|  | useful so far. In the next sections you'll learn how to add code and resources | 
|  | to it. | 
|  |  | 
|  |  | 
|  | ### Building and installing modules | 
|  |  | 
|  | Before we are going to jump into adding content to Foo, let's take a look on how | 
|  | to build and deploy the Monochrome bundle with the Foo DFM. The remainder of | 
|  | this guide assumes the environment variable `OUTDIR` is set to a properly | 
|  | configured GN build directory (e.g. `out/Debug`). | 
|  |  | 
|  | To build and install the Monochrome bundle to your connected device, run: | 
|  |  | 
|  | ```shell | 
|  | $ autoninja -C $OUTDIR monochrome_public_bundle | 
|  | $ $OUTDIR/bin/monochrome_public_bundle install -m foo | 
|  | ``` | 
|  |  | 
|  | This will install the `Foo` module, the `base` module, and all modules with an | 
|  | `AndroidManifest.xml` that: | 
|  | * Sets `<module dist:onDemand="false">`, or | 
|  | * Has `<dist:delivery>` conditions that are satisfied by the device being | 
|  | installed to. | 
|  |  | 
|  | *** note | 
|  | **Note:** The install script may install more modules than you specify, e.g. | 
|  | when there are default or conditionally installed modules (see | 
|  | [below](#conditional-install) for details). | 
|  | *** | 
|  |  | 
|  | You can then check that the install worked with: | 
|  |  | 
|  | ```shell | 
|  | $ adb shell dumpsys package org.chromium.chrome | grep splits | 
|  | >   splits=[base, config.en, foo] | 
|  | ``` | 
|  |  | 
|  | Then try installing the Monochrome bundle without your module and print the | 
|  | installed modules: | 
|  |  | 
|  | ```shell | 
|  | $ $OUTDIR/bin/monochrome_public_bundle install | 
|  | $ adb shell dumpsys package org.chromium.chrome | grep splits | 
|  | >   splits=[base, config.en] | 
|  | ``` | 
|  |  | 
|  | *** note | 
|  | The wrapper script's `install` command does approximately: | 
|  | ```sh | 
|  | java -jar third_party/android_build_tools/bundletool/cipd/bundletool.jar build-apks --output tmp.apks ... | 
|  | java -jar third_party/android_build_tools/bundletool/cipd/bundletool.jar install-apks --apks tmp.apks | 
|  | ``` | 
|  |  | 
|  | The `install-apks` command uses `adb install-multiple` under-the-hood. | 
|  | *** | 
|  |  | 
|  | ### Adding Java code | 
|  |  | 
|  | To make Foo useful, let's add some Java code to it. This section will walk you | 
|  | through the required steps. | 
|  |  | 
|  | First, define a module interface for Foo. This is accomplished by adding the | 
|  | `@ModuleInterface` annotation to the Foo interface. This annotation | 
|  | automatically creates a `FooModule` class that can be used later to install and | 
|  | access the module. To do this, add the following in the new file | 
|  | `//chrome/browser/foo/android/java/src/org/chromium/chrome/browser/foo/Foo.java`: | 
|  |  | 
|  | ```java | 
|  | package org.chromium.chrome.browser.foo; | 
|  |  | 
|  | import org.chromium.components.module_installer.builder.ModuleInterface; | 
|  |  | 
|  | /** Interface to call into Foo feature. */ | 
|  | @ModuleInterface(module = "foo", impl = "org.chromium.chrome.browser.FooImpl") | 
|  | public interface Foo { | 
|  | /** Magical function. */ | 
|  | void bar(); | 
|  | } | 
|  | ``` | 
|  |  | 
|  | Next, define an implementation that goes into the module in the new file | 
|  | `//chrome/browser/foo/internal/android/java/src/org/chromium/chrome/browser/foo/FooImpl.java`: | 
|  |  | 
|  | ```java | 
|  | package org.chromium.chrome.browser.foo; | 
|  |  | 
|  | import org.chromium.base.Log; | 
|  |  | 
|  | public class FooImpl implements Foo { | 
|  | @Override | 
|  | public void bar() { | 
|  | Log.i("FOO", "bar in module"); | 
|  | } | 
|  | } | 
|  | ``` | 
|  |  | 
|  | You can then use this provider to access the module if it is installed. To test | 
|  | that, instantiate Foo and call `bar()` somewhere in Chrome: | 
|  |  | 
|  | ```java | 
|  | if (FooModule.isInstalled()) { | 
|  | FooModule.getImpl().bar(); | 
|  | } else { | 
|  | Log.i("FOO", "module not installed"); | 
|  | } | 
|  | ``` | 
|  |  | 
|  | The interface has to be available regardless of whether the Foo DFM is present. | 
|  | Therefore, put those classes into the base module, creating a new public | 
|  | build target in: `//chrome/browser/foo/BUILD.gn`: | 
|  |  | 
|  | ```gn | 
|  | import("//build/config/android/rules.gni") | 
|  |  | 
|  | android_library("java") { | 
|  | sources = [ | 
|  | "android/java/src/org/chromium/chrome/browser/foo/Foo.java", | 
|  | ] | 
|  | deps = [ | 
|  | "//components/module_installer/android:module_installer_java", | 
|  | "//components/module_installer/android:module_interface_java", | 
|  | ] | 
|  | annotation_processor_deps = | 
|  | [ "//components/module_installer/android:module_interface_processor" ] | 
|  | } | 
|  | ``` | 
|  |  | 
|  | Then, depend on this target from where it is used as usual. For example, if the | 
|  | caller is in `chrome_java in //chrome/android/BUILD.gn`: | 
|  |  | 
|  | ```gn | 
|  | ... | 
|  | android_library("chrome_java") { | 
|  | deps =[ | 
|  | ... | 
|  | "//chrome/browser/foo:java", | 
|  | ... | 
|  | ] | 
|  | } | 
|  | ... | 
|  | ``` | 
|  |  | 
|  | The actual implementation, however, should go into the Foo DFM. For this | 
|  | purpose, create a new file `//chrome/browser/foo/internal/BUILD.gn` and | 
|  | make a library with the module Java code in it: | 
|  |  | 
|  | ```gn | 
|  | import("//build/config/android/rules.gni") | 
|  |  | 
|  | android_library("java") { | 
|  | # Define like ordinary Java Android library. | 
|  | sources = [ | 
|  | "android/java/src/org/chromium/chrome/browser/foo/FooImpl.java", | 
|  | # Add other Java classes that should go into the Foo DFM here. | 
|  | ] | 
|  | deps = [ | 
|  | "//base:base_java", | 
|  | # Put other Chrome libs into the classpath so that you can call into them | 
|  | # from the Foo DFM. | 
|  | "//chrome/browser/bar:java", | 
|  | # The module can depend even on `chrome_java` due to factory magic, but this | 
|  | # is discouraged. Consider passing a delegate interface in instead. | 
|  | "//chrome/android:chrome_java", | 
|  | # Also, you'll need to depend on any //third_party or //components code you | 
|  | # are using in the module code. | 
|  | ] | 
|  | } | 
|  | ``` | 
|  |  | 
|  | Then, add this new library as a dependency of the Foo module descriptor in | 
|  | `//chrome/android/modules/foo/foo_module.gni`: | 
|  |  | 
|  | ```gn | 
|  | foo_module_desc = { | 
|  | ... | 
|  | java_deps = [ | 
|  | "//chrome/browser/foo/internal:java", | 
|  | ] | 
|  | } | 
|  | ``` | 
|  |  | 
|  | Finally, tell Android that your module is now containing code. Do that by | 
|  | removing the `android:hasCode="false"` attribute from the `<application>` tag in | 
|  | `//chrome/android/modules/foo/internal/java/AndroidManifest.xml`. You should be | 
|  | left with an empty tag like so: | 
|  |  | 
|  | ```xml | 
|  | ... | 
|  | <application /> | 
|  | ... | 
|  | ``` | 
|  |  | 
|  | Rebuild and install `monochrome_public_bundle`. Start Chrome and run through a | 
|  | flow that tries to executes `bar()`. Depending on whether you installed your | 
|  | module (`-m foo`) "`bar in module`" or "`module not installed`" is printed to | 
|  | logcat. Yay! | 
|  |  | 
|  | ### Adding pre-built native libraries | 
|  |  | 
|  | You can add a third-party native library (or any standalone library that doesn't | 
|  | depend on Chrome code) by adding it as a loadable module to the module descriptor in | 
|  | `//chrome/android/moduiles/foo/foo_module.gni`: | 
|  |  | 
|  | ```gn | 
|  | foo_module_desc = { | 
|  | ... | 
|  | loadable_modules_32_bit = [ "//path/to/32/bit/lib.so" ] | 
|  | loadable_modules_64_bit = [ "//path/to/64/bit/lib.so" ] | 
|  | } | 
|  | ``` | 
|  |  | 
|  | ### Adding Chrome native code | 
|  |  | 
|  | Chrome native code may be placed in a DFM. The easiest way to access native | 
|  | feature code is by calling it from Java via JNI. When a module is first | 
|  | accessed, its native library (or potentially libraries, if using a component | 
|  | build), are automatically opened by the DFM framework, and a feature-specific | 
|  | JNI method (supplied by the feature's implementation) is invoked. Hence, a | 
|  | module's Java code may freely use JNI to call module native code. | 
|  |  | 
|  | Using the module framework and JNI to access the native code eliminates concerns | 
|  | with DFM library file names (which vary across build variants), | 
|  | `android_dlopen_ext()` (needed to open feature libraries), and use of dlsym(). | 
|  |  | 
|  | This mechanism can be extended if necessary by DFM implementers to facilitate | 
|  | subsequent native-native calls, by having a JNI-called initialization method | 
|  | create instance of a object or factory, and register it through a call to the | 
|  | base module's native code (DFM native code can call base module code directly). | 
|  |  | 
|  | #### JNI | 
|  |  | 
|  | Read the `jni_generator` [docs](../third_party/jni_zero/README.md) before | 
|  | reading this section. | 
|  |  | 
|  | There are some subtleties to how JNI registration works with DFMs: | 
|  |  | 
|  | * Generated wrapper `ClassNameJni` classes are packaged into the DFM's dex file | 
|  | * The class containing the actual native definitions, | 
|  | `<module_name>_GEN_JNI.java`, is currently stored in the base module, but | 
|  | could be moved out | 
|  | * The `Natives` interface you provide will need to be annotated with your module | 
|  | name as an argument to `NativeMethods`, eg. `@NativeMethods("foo")`, resulting | 
|  | in a uniquely named `foo_GEN_JNI.java` | 
|  | * The DFM will need to provide a `generate_jni_registration` target | 
|  | that will generate all of the native registration functions | 
|  |  | 
|  | #### Calling DFM native code via JNI | 
|  |  | 
|  | A linker-assisted partitioning system automates the placement of code into | 
|  | either the main Chrome library or feature-specific .so libraries. Feature code | 
|  | may continue to make use of core Chrome code (eg. base::) without modification, | 
|  | but Chrome must call feature code through a virtual interface (any "direct" | 
|  | calls to the feature code from the main library will cause the feature code to | 
|  | be pulled back into the main library). | 
|  |  | 
|  | Partitioning is explained in [Android Native | 
|  | Libraries](android_native_libraries.md#partitioned-libraries). | 
|  |  | 
|  | First, build a module native interface. Supply a JNI method named | 
|  | `JNI_OnLoad_foo` for the module framework to call, in | 
|  | `//chrome/android/modules/foo/internal/entrypoints.cc`. This method is invoked | 
|  | on all Chrome build variants, including Monochrome (unlike base module JNI). | 
|  |  | 
|  | ```c++ | 
|  | #include "third_party/jni_zero/jni_zero_helper.h" | 
|  | #include "base/android/jni_utils.h" | 
|  | #include "chrome/android/modules/foo/internal/jni_registration.h" | 
|  |  | 
|  | extern "C" { | 
|  | // This JNI registration method is found and called by module framework code. | 
|  | JNI_ZERO_BOUNDARY_EXPORT bool JNI_OnLoad_foo(JNIEnv* env) { | 
|  | if (!foo::RegisterNatives(env)) { | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | }  // extern "C" | 
|  | ``` | 
|  |  | 
|  | Next, include the module entrypoint and related pieces in the build config at | 
|  | `//chrome/android/modules/foo/internal/BUILD.gn`: | 
|  |  | 
|  | ```gn | 
|  | import("//build/config/android/rules.gni") | 
|  | import("//chrome/android/modules/buildflags.gni") | 
|  | ... | 
|  |  | 
|  | # Put the JNI entrypoint in a component, so that the component build has a | 
|  | # library to include in the foo module. This makes things feel consistent with | 
|  | # a release build. | 
|  | component("foo") { | 
|  | sources = [ | 
|  | "entrypoints.cc", | 
|  | ] | 
|  | deps = [ | 
|  | ":jni_registration", | 
|  | "//base", | 
|  | "//chrome/browser/foo/internal:native", | 
|  | ] | 
|  |  | 
|  | # Instruct the compiler to flag exported entrypoint function as belonging in | 
|  | # foo's library. The linker will use this information when creating the | 
|  | # native libraries. The partition name must be <feature>_partition. | 
|  | if (use_native_partitions) { | 
|  | cflags = [ "-fsymbol-partition=foo_partition" ] | 
|  | } | 
|  | } | 
|  |  | 
|  | # Generate JNI registration for the methods called by the Java side. Note the | 
|  | # no_transitive_deps argument, which ensures that JNI is generated for only the | 
|  | # specified Java target, and not all its transitive deps (which could include | 
|  | # the base module). | 
|  | generate_jni_registration("jni_registration") { | 
|  | targets = [ "//chrome/browser/foo/internal:java" ] | 
|  | namespace = "foo" | 
|  | no_transitive_deps = true | 
|  | manual_jni_registration = true | 
|  | } | 
|  |  | 
|  | # This group is a convenience alias representing the module's native code, | 
|  | # allowing it to be named "native" for clarity in module descriptors. | 
|  | group("native") { | 
|  | deps = [ | 
|  | ":foo", | 
|  | ] | 
|  | } | 
|  | ``` | 
|  |  | 
|  | Now, over to the implementation of the module. These are the parts that | 
|  | shouldn't know or care whether they're living in a module or not. | 
|  |  | 
|  | Add a stub implementation in | 
|  | `//chrome/browser/foo/internal/android/foo_impl.cc`: | 
|  |  | 
|  | ```c++ | 
|  | #include "base/logging.h" | 
|  | #include "chrome/browser/foo/internal/jni_headers/FooImpl_jni.h" | 
|  |  | 
|  | static int JNI_FooImpl_Execute(JNIEnv* env) { | 
|  | LOG(INFO) << "Running foo feature code!"; | 
|  | return 123; | 
|  | } | 
|  | ``` | 
|  |  | 
|  | And, the associated build config in | 
|  | `//chrome/browser/foo/internal/BUILD.gn`: | 
|  |  | 
|  | ```gn | 
|  | import("//build/config/android/rules.gni") | 
|  |  | 
|  | ... | 
|  |  | 
|  | source_set("native") { | 
|  | sources = [ | 
|  | "android/foo_impl.cc", | 
|  | ] | 
|  |  | 
|  | deps = [ | 
|  | ":jni_headers", | 
|  | "//base", | 
|  | ] | 
|  | } | 
|  |  | 
|  | generate_jni("jni_headers") { | 
|  | sources = [ | 
|  | "android/java/src/org/chromium/chrome/browser/foo/FooImpl.java", | 
|  | ] | 
|  | } | 
|  | ``` | 
|  |  | 
|  | With a declaration of the native method on the Java side: | 
|  |  | 
|  | ```java | 
|  | public class FooImpl implements Foo { | 
|  | ... | 
|  |  | 
|  | @NativeMethods("foo") | 
|  | interface Natives { | 
|  | int execute(); | 
|  | } | 
|  | } | 
|  | ``` | 
|  |  | 
|  | Finally, augment the module descriptor in | 
|  | `//chrome/android/modules/foo/foo_module.gni` with the native dependencies: | 
|  |  | 
|  | ```gn | 
|  | foo_module_desc = { | 
|  | ... | 
|  | native_deps = [ | 
|  | "//chrome/android/modules/foo/internal:native", | 
|  | "//chrome/browser/foo/internal:native", | 
|  | ] | 
|  | load_native_on_get_impl = true | 
|  | } | 
|  | ``` | 
|  |  | 
|  | If `load_native_on_get_impl` is set to `true` then Chrome automatically loads | 
|  | Foo DFM's native libraries and PAK file resources when `FooModule.getImpl()` is | 
|  | called for the first time. The loading requires Chrome's main native libraries | 
|  | to be loaded. If you wish to call `FooModule.getImpl()` earlier than that, then | 
|  | you'd need to set `load_native_on_get_impl` to `false`, and manage native | 
|  | libraries / resources loading yourself (potentially, on start-up and on install, | 
|  | or on use). | 
|  |  | 
|  | #### Calling feature module native code from base the module | 
|  |  | 
|  | If planning to use direct native-native calls into DFM code, then the module | 
|  | should have a purely virtual interface available. The main module can obtain a | 
|  | pointer to a DFM-created object or factory (implemented by the feature), and | 
|  | call its virtual methods. | 
|  |  | 
|  | Ideally, the interface to the feature will avoid feature-specific types. If a | 
|  | feature defines complex data types, and uses them in its own interface, then it's | 
|  | likely the main library will utilize the code backing these types. That code, | 
|  | and anything it references, will in turn be pulled back into the main library, | 
|  | negating the intent to house code in the DFM. | 
|  |  | 
|  | Therefore, designing the feature interface to use C types, C++ standard types, | 
|  | or classes that aren't expected to move out of Chrome's main library is ideal. | 
|  | If feature-specific classes are needed, they simply need to avoid referencing | 
|  | feature library internals. | 
|  |  | 
|  | ### Adding Android resources | 
|  |  | 
|  | In this section we will add the required build targets to add Android resources | 
|  | to the Foo DFM. | 
|  |  | 
|  | First, add a resources target to | 
|  | `//chrome/browser/foo/internal/BUILD.gn` and add it as a dependency on | 
|  | Foo's `java` target in the same file: | 
|  |  | 
|  | ```gn | 
|  | ... | 
|  | android_resources("java_resources") { | 
|  | # Define like ordinary Android resources target. | 
|  | ... | 
|  | custom_package = "org.chromium.chrome.browser.foo" | 
|  | } | 
|  | ... | 
|  | android_library("java") { | 
|  | ... | 
|  | deps = [ | 
|  | ":java_resources", | 
|  | ] | 
|  | } | 
|  | ``` | 
|  |  | 
|  | To add strings follow steps | 
|  | [here](http://dev.chromium.org/developers/design-documents/ui-localization) to | 
|  | add new Java GRD file. Then create | 
|  | `//chrome/browser/foo/internal/android/resources/strings/android_foo_strings.grd` as | 
|  | follows: | 
|  |  | 
|  | ```xml | 
|  | <?xml version="1.0" encoding="UTF-8"?> | 
|  | <grit current_release="1" latest_public_release="0"> | 
|  | <outputs> | 
|  | <output | 
|  | filename="values-am/android_foo_strings.xml" | 
|  | lang="am" | 
|  | type="android" /> | 
|  | <!-- List output file for all other supported languages. See | 
|  | //chrome/browser/ui/android/strings/android_chrome_strings.grd for the | 
|  | full list. --> | 
|  | ... | 
|  | </outputs> | 
|  | <translations> | 
|  | <file lang="am" path="vr_translations/android_foo_strings_am.xtb" /> | 
|  | <!-- Here, too, list XTB files for all other supported languages. --> | 
|  | ... | 
|  | </translations> | 
|  | <release seq="1"> | 
|  | <messages fallback_to_english="true"> | 
|  | <message name="IDS_BAR_IMPL_TEXT" desc="Magical string."> | 
|  | impl | 
|  | </message> | 
|  | </messages> | 
|  | </release> | 
|  | </grit> | 
|  | ``` | 
|  |  | 
|  | Then, create a new GRD target and add it as a dependency on `java_resources` in | 
|  | `//chrome/browser/foo/internal/BUILD.gn`: | 
|  |  | 
|  | ```gn | 
|  | ... | 
|  | java_strings_grd("java_strings_grd") { | 
|  | defines = chrome_grit_defines | 
|  | grd_file = "android/resources/strings/android_foo_strings.grd" | 
|  | } | 
|  | ... | 
|  | android_resources("java_resources") { | 
|  | ... | 
|  | deps = [":java_strings_grd"] | 
|  | custom_package = "org.chromium.chrome.browser.foo" | 
|  | } | 
|  | ... | 
|  | ``` | 
|  |  | 
|  | You can then access Foo's resources using the | 
|  | `org.chromium.chrome.browser.foo.R` class. To do this change | 
|  | `//chrome/browser/foo/internal/android/java/src/org/chromium/chrome/browser/foo/FooImpl.java` | 
|  | to: | 
|  |  | 
|  | ```java | 
|  | package org.chromium.chrome.browser.foo; | 
|  |  | 
|  | import org.chromium.base.ContextUtils; | 
|  | import org.chromium.base.Log; | 
|  | import org.chromium.chrome.browser.foo.R; | 
|  |  | 
|  | public class FooImpl implements Foo { | 
|  | @Override | 
|  | public void bar() { | 
|  | Log.i("FOO", ContextUtils.getApplicationContext().getString( | 
|  | R.string.bar_impl_text)); | 
|  | } | 
|  | } | 
|  | ``` | 
|  |  | 
|  | ### Adding non-string native resources | 
|  |  | 
|  | This section describes how to add non-string native resources to Foo DFM. | 
|  | Key ideas: | 
|  |  | 
|  | * The compiled resource file shipped with the DFM is `foo_resourcess.pak`. | 
|  | * At run time, native resources need to be loaded before use. Also, DFM native | 
|  | resources can only be used from the Browser process. | 
|  |  | 
|  | #### Creating PAK file | 
|  |  | 
|  | Two ways to create `foo_resourcess.pak` (using GRIT) are: | 
|  |  | 
|  | 1. (Preferred) Use `foo_resourcess.grd` to refer to individual files (e.g., | 
|  | images, HTML, JS, or CSS) and assigns resource text IDs. `foo_resourcess.pak` | 
|  | must have an entry in `/tools/gritsettings/resource_ids.spec`. | 
|  | 1. Combine existing .pak files via `repack` rules in GN build files. This is | 
|  | done by the DevUI DFM, which aggregates resources from many DevUI pages. | 
|  |  | 
|  | #### Loading PAK file | 
|  |  | 
|  | At runtime, `foo_resources.pak` needs to be loaded (memory-mapped) before any of | 
|  | its resource gets used. Alternatives to do this are: | 
|  |  | 
|  | 1. (Simplest) Specify native resources (with native libraries if any exist) to | 
|  | be automatically loaded on first call to `FooModule.getImpl()`. This behavior | 
|  | is specified via `load_native_on_get_impl = true` in `foo_module_desc`. | 
|  | 1. In Java code, call `FooModule.ensureNativeLoaded()`. | 
|  | 1. In C++ code, use JNI to call `FooModule.ensureNativeLoaded()`. The code to do | 
|  | this can be placed in a helper class, which can also have JNI calls to | 
|  | `FooModule.isInstalled()` and `FooModule.installModule()`. | 
|  |  | 
|  | #### Cautionary notes | 
|  |  | 
|  | Compiling `foo_resources.pak` auto-generates `foo_resources.h`, which defines | 
|  | textual resource IDs, e.g., `IDR_FOO_HTML`. C++ code then uses these IDs to get | 
|  | resource bytes. Unfortunately, this behavior is fragile: If `IDR_FOO_HTML` is | 
|  | accessed before the Foo DFM is (a) installed, or (b) loaded, then runtime error | 
|  | ensues! Some mitigation strategies are as follows: | 
|  |  | 
|  | * (Ideal) Access Foo DFM's native resources only from code in Foo DFM's native | 
|  | libraries. So by the time that `IDR_FOO_HTML` is accessed, everything is | 
|  | already in place! This isn't always possible; henceforth we assume that | 
|  | `IDR_FOO_HTML` is accessed by code in the base DFM. | 
|  | * Before accessing IDR_FOO_HTML, ensure Foo DFM is installed and loaded. The | 
|  | latter can use `FooModule.ensureNativeLoaded()` (needs to be called from | 
|  | Browser thread). | 
|  | * Use inclusion of `foo_resources.h` to restrict availability of `IDR_FOO_HTML`. | 
|  | Only C++ files dedicated to "DFM-gated code" (code that runs only when its DFM | 
|  | is installed and loaded) should include `foo_resources.h`. | 
|  |  | 
|  | #### Associating native resources with DFM | 
|  |  | 
|  | Here are the main GN changes to specify PAK files and default loading behavior | 
|  | for a DFM's native resources: | 
|  |  | 
|  | ```gn | 
|  | foo_module_desc = { | 
|  | ... | 
|  | paks = [ "$root_gen_dir/chrome/browser/foo/internal/foo_resourcess.pak" ] | 
|  | pak_deps = [ "//chrome/browser/foo/internal:foo_paks" ] | 
|  | load_native_on_get_impl = true | 
|  | } | 
|  | ``` | 
|  |  | 
|  | Note that `load_native_on_get_impl` specifies both native libraries and native | 
|  | resources. | 
|  |  | 
|  |  | 
|  | ### Module install | 
|  |  | 
|  | So far, we have installed the Foo DFM as a true split (`-m foo` option on the | 
|  | install script). In production, however, we have to explicitly install the Foo | 
|  | DFM for users to get it. There are three install options: _on-demand_, | 
|  | _deferred_ and _conditional_. | 
|  |  | 
|  | #### On-demand install | 
|  |  | 
|  | On-demand requesting a module will try to download and install the | 
|  | module as soon as possible regardless of whether the user is on a metered | 
|  | connection or whether they have turned updates off in the Play Store app. | 
|  |  | 
|  | You can use the autogenerated module class to on-demand install the module like | 
|  | so: | 
|  |  | 
|  | ```java | 
|  | FooModule.install((success) -> { | 
|  | if (success) { | 
|  | FooModule.getImpl().bar(); | 
|  | } | 
|  | }); | 
|  | ``` | 
|  |  | 
|  | **Optionally**, you can show UI telling the user about the install flow. For | 
|  | this, add a function like the one below. Note, it is possible | 
|  | to only show either one of the  install, failure and success UI or any | 
|  | combination of the three. | 
|  |  | 
|  | ```java | 
|  | public static void installModuleWithUi( | 
|  | Tab tab, OnModuleInstallFinishedListener onFinishedListener) { | 
|  | ModuleInstallUi ui = | 
|  | new ModuleInstallUi( | 
|  | tab, | 
|  | R.string.foo_module_title, | 
|  | new ModuleInstallUi.FailureUiListener() { | 
|  | @Override | 
|  | public void onFailureUiResponse(retry) { | 
|  | if (retry) { | 
|  | installModuleWithUi(tab, onFinishedListener); | 
|  | } else { | 
|  | onFinishedListener.onFinished(false); | 
|  | } | 
|  | } | 
|  | }); | 
|  | // At the time of writing, shows toast informing user about install start. | 
|  | ui.showInstallStartUi(); | 
|  | FooModule.install( | 
|  | (success) -> { | 
|  | if (!success) { | 
|  | // At the time of writing, shows infobar allowing user | 
|  | // to retry install. | 
|  | ui.showInstallFailureUi(); | 
|  | return; | 
|  | } | 
|  | // At the time of writing, shows toast informing user about | 
|  | // install success. | 
|  | ui.showInstallSuccessUi(); | 
|  | onFinishedListener.onFinished(true); | 
|  | }); | 
|  | } | 
|  | ``` | 
|  |  | 
|  | To test on-demand install, "fake-install" the DFM. It's fake because | 
|  | the DFM is not installed as a true split. Instead it will be emulated by play | 
|  | core's `--local-testing` [mode][play-core-local-testing]. | 
|  | Fake-install and launch Chrome with the following command: | 
|  |  | 
|  | ```shell | 
|  | $ $OUTDIR/bin/monochrome_public_bundle install -f foo | 
|  | $ $OUTDIR/bin/monochrome_public_bundle launch | 
|  | ``` | 
|  |  | 
|  | When running the install code, the Foo DFM module will be emulated. | 
|  | This will be the case in production right after installing the module. Emulation | 
|  | will last until Play Store has a chance to install your module as a true split. | 
|  | This usually takes about a day. After it has been installed, it will be updated | 
|  | atomically alongside Chrome. Always check that it is installed and available | 
|  | before invoking code within the DFM. | 
|  |  | 
|  | *** note | 
|  | **Warning:** There are subtle differences between emulating a module and | 
|  | installing it as a true split. We therefore recommend that you always test both | 
|  | install methods. | 
|  | *** | 
|  |  | 
|  | *** note | 
|  | To simplify development, the DevUI DFM (dev_ui) is installed by default, i.e., | 
|  | `-m dev_ui` is implied by default. This is overridden by: | 
|  | * `--no-module dev_ui`, to test error from missing DevUI, | 
|  | * `-f dev_ui`, for fake module install. | 
|  | *** | 
|  |  | 
|  | #### Deferred install | 
|  |  | 
|  | Deferred install means that the DFM is installed in the background when the | 
|  | device is on an unmetered connection and charging. The DFM will only be | 
|  | available after Chrome restarts. When deferred installing a module it will | 
|  | not be faked installed. | 
|  |  | 
|  | To defer install Foo do the following: | 
|  |  | 
|  | ```java | 
|  | FooModule.installDeferred(); | 
|  | ``` | 
|  |  | 
|  | #### Conditional install | 
|  |  | 
|  | Conditional install means the DFM will be installed automatically upon first | 
|  | installing or updating Chrome if the device supports a particular feature. | 
|  | Conditional install is configured in the module's manifest. To install your | 
|  | module on all Daydream-ready devices for instance, your | 
|  | `//chrome/android/modules/foo/internal/java/AndroidManifest.xml` should look | 
|  | like this: | 
|  |  | 
|  | ```xml | 
|  | <?xml version="1.0" encoding="utf-8"?> | 
|  | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | 
|  | xmlns:dist="http://schemas.android.com/apk/distribution" | 
|  | featureSplit="foo"> | 
|  |  | 
|  | <dist:module | 
|  | dist:instant="false" | 
|  | dist:title="@string/foo_module_title"> | 
|  | <dist:fusing dist:include="true" /> | 
|  | <dist:delivery> | 
|  | <dist:install-time> | 
|  | <dist:conditions> | 
|  | <dist:device-feature | 
|  | dist:name="android.hardware.vr.high_performance" /> | 
|  | </dist:conditions> | 
|  | </dist:install-time> | 
|  | <!-- Allows on-demand or deferred install on non-Daydream-ready | 
|  | devices. --> | 
|  | <dist:on-demand /> | 
|  | </dist:delivery> | 
|  | </dist:module> | 
|  |  | 
|  | <application /> | 
|  | </manifest> | 
|  | ``` | 
|  |  | 
|  | You can also specify no conditions to have your module always installed. | 
|  | You might want to do this in order to delay the performance implications | 
|  | of loading your module until its first use (true only on Android O+ where | 
|  | [android:isolatedSplits](https://developer.android.com/reference/android/R.attr#isolatedSplits) | 
|  | is supported. See [go/isolated-splits-dev-guide](http://go/isolated-splits-dev-guide) | 
|  | (googlers only). | 
|  |  | 
|  | ### chrome_public_apk and Integration Tests | 
|  |  | 
|  | To make the Foo feature available in the non-bundle `chrome_public_apk` | 
|  | target, add the `java` target to the template in | 
|  | `//chrome/android/chrome_public_apk_tmpl.gni` like so: | 
|  |  | 
|  | ```gn | 
|  | # Add to where "chrome_all_java" is added: | 
|  | if (!_is_bundle) { | 
|  | deps += [ "//chrome/browser/foo/internal:java" ] | 
|  | } | 
|  | } | 
|  | ``` | 
|  |  | 
|  | You may also have to add `java` as a dependency of | 
|  | `//chrome/android/javatests/chrome_test_java_org.chromium.chrome.browser.foo` | 
|  | if you want to call into Foo from test code. | 
|  |  | 
|  | [play-core-local-testing]: https://developer.android.com/guide/playcore/feature-delivery/on-demand#local-testing |