blob: 4a280a1b756d9a38425f4fdc69864c6f77c1ffb2 [file] [log] [blame] [view]
Tibor Goldschwendt19364ba2019-04-10 15:59:551# Dynamic Feature Modules (DFMs)
2
3[Android App bundles and Dynamic Feature Modules (DFMs)](https://developer.android.com/guide/app-bundle)
4is a Play Store feature that allows delivering pieces of an app when they are
5needed rather than at install time. We use DFMs to modularize Chrome and make
6Chrome's install size smaller.
7
8[TOC]
9
10
11## Limitations
12
Tibor Goldschwendt68c5f722019-08-01 15:10:1513DFMs have the following limitations:
Tibor Goldschwendt19364ba2019-04-10 15:59:5514
15* **WebView:** We don't support DFMs for WebView. If your feature is used by
Tibor Goldschwendt68c5f722019-08-01 15:10:1516 WebView you cannot put it into a DFM.
Tibor Goldschwendt19364ba2019-04-10 15:59:5517* **Android K:** DFMs are based on split APKs, a feature introduced in Android
18 L. Therefore, we don't support DFMs on Android K. As a workaround
Tibor Goldschwendt68c5f722019-08-01 15:10:1519 you can add your feature to the Android K APK build. See below for details.
Tibor Goldschwendt19364ba2019-04-10 15:59:5520
21## Getting started
22
23This guide walks you through the steps to create a DFM called _Foo_ and add it
Tibor Goldschwendtaef8e392019-07-19 16:39:1024to the Chrome bundles.
Tibor Goldschwendt19364ba2019-04-10 15:59:5525
26*** note
27**Note:** To make your own module you'll essentially have to replace every
28instance of `foo`/`Foo`/`FOO` with `your_feature_name`/`YourFeatureName`/
29`YOUR_FEATURE_NAME`.
30***
31
32
33### Create DFM target
34
35DFMs are APKs. They have a manifest and can contain Java and native code as well
36as resources. This section walks you through creating the module target in our
37build system.
38
Tibor Goldschwendt68c5f722019-08-01 15:10:1539First, create the file
40`//chrome/android/features/foo/internal/java/AndroidManifest.xml` and add:
Tibor Goldschwendt19364ba2019-04-10 15:59:5541
42```xml
Tibor Goldschwendt68c5f722019-08-01 15:10:1543<?xml version="1.0" encoding="utf-8"?>
Tibor Goldschwendt19364ba2019-04-10 15:59:5544<manifest xmlns:android="http://schemas.android.com/apk/res/android"
45 xmlns:dist="http://schemas.android.com/apk/distribution"
Tibor Goldschwendt5172118f2019-06-24 21:57:4746 featureSplit="foo">
Tibor Goldschwendt19364ba2019-04-10 15:59:5547
Tibor Goldschwendt19364ba2019-04-10 15:59:5548 <!-- dist:onDemand="true" makes this a separately installed module.
49 dist:onDemand="false" would always install the module alongside the
50 rest of Chrome. -->
51 <dist:module
52 dist:onDemand="true"
53 dist:title="@string/foo_module_title">
Ben Mason932dfd12019-08-28 02:19:2754 <!-- This will prevent the module to become part of the Android K
55 build in case we ever want to use bundles on Android K. -->
56 <dist:fusing dist:include="false" />
Tibor Goldschwendt19364ba2019-04-10 15:59:5557 </dist:module>
58
Samuel Huang39c7db632019-05-15 14:57:1859 <!-- Remove android:hasCode="false" when adding Java code. -->
60 <application android:hasCode="false" />
Tibor Goldschwendt19364ba2019-04-10 15:59:5561</manifest>
62```
63
64Then, add a package ID for Foo so that Foo's resources have unique identifiers.
65For this, add a new ID to
Tibor Goldschwendtaef8e392019-07-19 16:39:1066`//chrome/android/modules/chrome_feature_modules.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:5567
68```gn
69resource_packages_id_mapping = [
70 ...,
71 "foo=0x{XX}", # Set {XX} to next lower hex number.
72]
73```
74
Tibor Goldschwendtaef8e392019-07-19 16:39:1075Next, create a descriptor configuring the Foo module. To do this, create
76`//chrome/android/features/foo/foo_module.gni` and add the following:
Tibor Goldschwendt19364ba2019-04-10 15:59:5577
78```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:1079foo_module_desc = {
80 name = "foo"
Tibor Goldschwendt68c5f722019-08-01 15:10:1581 android_manifest =
82 "//chrome/android/features/foo/internal/java/AndroidManifest.xml"
Tibor Goldschwendt19364ba2019-04-10 15:59:5583}
84```
85
Tibor Goldschwendtaef8e392019-07-19 16:39:1086Then, add the module descriptor to the appropriate descriptor list in
87//chrome/android/modules/chrome_feature_modules.gni, e.g. the Chrome Modern
88list:
Tibor Goldschwendt19364ba2019-04-10 15:59:5589
90```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:1091import("//chrome/android/features/foo/foo_module.gni")
Tibor Goldschwendt19364ba2019-04-10 15:59:5592...
Tibor Goldschwendtaef8e392019-07-19 16:39:1093chrome_modern_module_descs += [ foo_module_desc ]
Tibor Goldschwendt19364ba2019-04-10 15:59:5594```
95
96The next step is to add Foo to the list of feature modules for UMA recording.
97For this, add `foo` to the `AndroidFeatureModuleName` in
98`//tools/metrics/histograms/histograms.xml`:
99
100```xml
101<histogram_suffixes name="AndroidFeatureModuleName" ...>
102 ...
103 <suffix name="foo" label="Super Duper Foo Module" />
104 ...
105</histogram_suffixes>
106```
107
108<!--- TODO(tiborg): Add info about install UI. -->
109Lastly, give your module a title that Chrome and Play can use for the install
110UI. To do this, add a string to
111`//chrome/android/java/strings/android_chrome_strings.grd`:
112
113```xml
114...
115<message name="IDS_FOO_MODULE_TITLE"
116 desc="Text shown when the Foo module is referenced in install start, success,
117 failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to
118 'Installing Foo for Chrome').">
119 Foo
120</message>
121...
122```
123
Samuel Huang7f2b53752019-05-23 15:10:05124*** note
125**Note:** This is for module title only. Other strings specific to the module
126should go in the module, not here (in the base module).
127***
128
Tibor Goldschwendt19364ba2019-04-10 15:59:55129Congrats! You added the DFM Foo to Monochrome. That is a big step but not very
130useful so far. In the next sections you'll learn how to add code and resources
131to it.
132
133
134### Building and installing modules
135
136Before we are going to jump into adding content to Foo, let's take a look on how
137to build and deploy the Monochrome bundle with the Foo DFM. The remainder of
138this guide assumes the environment variable `OUTDIR` is set to a properly
139configured GN build directory (e.g. `out/Debug`).
140
141To build and install the Monochrome bundle to your connected device, run:
142
143```shell
144$ autoninja -C $OUTDIR monochrome_public_bundle
145$ $OUTDIR/bin/monochrome_public_bundle install -m base -m foo
146```
147
148This will install Foo alongside the rest of Chrome. The rest of Chrome is called
149_base_ module in the bundle world. The Base module will always be put on the
150device when initially installing Chrome.
151
152*** note
153**Note:** You have to specify `-m base` here to make it explicit which modules
154will be installed. If you only specify `-m foo` the command will fail. It is
155also possible to specify no modules. In that case, the script will install the
156set of modules that the Play Store would install when first installing Chrome.
157That may be different than just specifying `-m base` if we have non-on-demand
158modules.
159***
160
161You can then check that the install worked with:
162
163```shell
164$ adb shell dumpsys package org.chromium.chrome | grep splits
165> splits=[base, config.en, foo]
166```
167
168Then try installing the Monochrome bundle without your module and print the
169installed modules:
170
171```shell
172$ $OUTDIR/bin/monochrome_public_bundle install -m base
173$ adb shell dumpsys package org.chromium.chrome | grep splits
174> splits=[base, config.en]
175```
176
177
178### Adding java code
179
180To make Foo useful, let's add some Java code to it. This section will walk you
181through the required steps.
182
Tibor Goldschwendt573cf3022019-05-10 17:23:30183First, define a module interface for Foo. This is accomplished by adding the
184`@ModuleInterface` annotation to the Foo interface. This annotation
185automatically creates a `FooModule` class that can be used later to install and
186access the module. To do this, add the following in the new file
Tibor Goldschwendt19364ba2019-04-10 15:59:55187`//chrome/android/features/foo/public/java/src/org/chromium/chrome/features/foo/Foo.java`:
188
189```java
190package org.chromium.chrome.features.foo;
191
Tibor Goldschwendt573cf3022019-05-10 17:23:30192import org.chromium.components.module_installer.ModuleInterface;
193
Tibor Goldschwendt19364ba2019-04-10 15:59:55194/** Interface to call into Foo feature. */
Tibor Goldschwendt573cf3022019-05-10 17:23:30195@ModuleInterface(module = "foo", impl = "org.chromium.chrome.features.FooImpl")
Tibor Goldschwendt19364ba2019-04-10 15:59:55196public interface Foo {
197 /** Magical function. */
198 void bar();
199}
200```
201
202*** note
203**Note:** To reflect the separation from "Chrome browser" code, features should
204be defined in their own package name, distinct from the chrome package - i.e.
205`org.chromium.chrome.features.<feature-name>`.
206***
207
208Next, define an implementation that goes into the module in the new file
Tibor Goldschwendt68c5f722019-08-01 15:10:15209`//chrome/android/features/foo/internal/java/src/org/chromium/chrome/features/foo/FooImpl.java`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55210
211```java
212package org.chromium.chrome.features.foo;
213
214import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30215import org.chromium.base.annotations.UsedByReflection;
Tibor Goldschwendt19364ba2019-04-10 15:59:55216
Tibor Goldschwendt573cf3022019-05-10 17:23:30217@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55218public class FooImpl implements Foo {
219 @Override
220 public void bar() {
221 Log.i("FOO", "bar in module");
222 }
223}
224```
225
Tibor Goldschwendt19364ba2019-04-10 15:59:55226You can then use this provider to access the module if it is installed. To test
227that, instantiate Foo and call `bar()` somewhere in Chrome:
228
229```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30230if (FooModule.isInstalled()) {
231 FooModule.getImpl().bar();
Tibor Goldschwendt19364ba2019-04-10 15:59:55232} else {
233 Log.i("FOO", "module not installed");
234}
235```
236
Tibor Goldschwendt573cf3022019-05-10 17:23:30237The interface has to be available regardless of whether the Foo DFM is present.
238Therefore, put those classes into the base module. For this create a list of
239those Java files in
Tibor Goldschwendt19364ba2019-04-10 15:59:55240`//chrome/android/features/foo/public/foo_public_java_sources.gni`:
241
242```gn
243foo_public_java_sources = [
244 "//chrome/android/features/foo/public/java/src/org/chromium/chrome/features/foo/Foo.java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55245]
246```
247
248Then add this list to `chrome_java in //chrome/android/BUILD.gn`:
249
250```gn
251...
Tibor Goldschwendt68c5f722019-08-01 15:10:15252import("//chrome/android/features/foo/public/foo_public_java_sources.gni")
Tibor Goldschwendt19364ba2019-04-10 15:59:55253...
254android_library("chrome_java") {
255 ...
256 java_files += foo_public_java_sources
257}
258...
259```
260
261The actual implementation, however, should go into the Foo DFM. For this
Tibor Goldschwendt68c5f722019-08-01 15:10:15262purpose, create a new file `//chrome/android/features/foo/internal/BUILD.gn` and
263make a library with the module Java code in it:
Tibor Goldschwendt19364ba2019-04-10 15:59:55264
265```gn
266import("//build/config/android/rules.gni")
267
268android_library("java") {
269 # Define like ordinary Java Android library.
270 java_files = [
271 "java/src/org/chromium/chrome/features/foo/FooImpl.java",
272 # Add other Java classes that should go into the Foo DFM here.
273 ]
274 # Put other Chrome libs into the classpath so that you can call into the rest
275 # of Chrome from the Foo DFM.
Fred Mellob32b3022019-06-21 18:10:11276 deps = [
Tibor Goldschwendt19364ba2019-04-10 15:59:55277 "//base:base_java",
278 "//chrome/android:chrome_java",
279 # etc.
280 # Also, you'll need to depend on any //third_party or //components code you
281 # are using in the module code.
282 ]
283}
284```
285
Tibor Goldschwendtaef8e392019-07-19 16:39:10286Then, add this new library as a dependency of the Foo module descriptor in
287`//chrome/android/features/foo/foo_module.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55288
289```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10290foo_module_desc = {
Tibor Goldschwendt19364ba2019-04-10 15:59:55291 ...
Tibor Goldschwendtaef8e392019-07-19 16:39:10292 java_deps = [
Tibor Goldschwendt68c5f722019-08-01 15:10:15293 "//chrome/android/features/foo/internal:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55294 ]
295}
296```
297
298Finally, tell Android that your module is now containing code. Do that by
Samuel Huang39c7db632019-05-15 14:57:18299removing the `android:hasCode="false"` attribute from the `<application>` tag in
Tibor Goldschwendt68c5f722019-08-01 15:10:15300`//chrome/android/features/foo/internal/java/AndroidManifest.xml`. You should be
301left with an empty tag like so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55302
303```xml
304...
305 <application />
306...
307```
308
309Rebuild and install `monochrome_public_bundle`. Start Chrome and run through a
310flow that tries to executes `bar()`. Depending on whether you installed your
311module (`-m foo`) "`bar in module`" or "`module not installed`" is printed to
312logcat. Yay!
313
Christopher Grant8fea5a12019-07-31 19:12:31314### Adding third-party native code
Tibor Goldschwendt19364ba2019-04-10 15:59:55315
Christopher Grant8fea5a12019-07-31 19:12:31316You can add a third-party native library (or any standalone library that doesn't
317depend on Chrome code) by adding it as a loadable module to the module descriptor in
318`//chrome/android/features/foo/foo_module.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55319
320```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10321foo_module_desc = {
Tibor Goldschwendt19364ba2019-04-10 15:59:55322 ...
Tibor Goldschwendtaef8e392019-07-19 16:39:10323 loadable_modules_32_bit = [ "//path/to/32/bit/lib.so" ]
324 loadable_modules_64_bit = [ "//path/to/64/bit/lib.so" ]
Tibor Goldschwendt19364ba2019-04-10 15:59:55325}
326```
327
Christopher Grant8fea5a12019-07-31 19:12:31328### Adding Chrome native code
Tibor Goldschwendt19364ba2019-04-10 15:59:55329
Christopher Grant8fea5a12019-07-31 19:12:31330Chrome native code may be placed in a DFM.
331
332A linker-assisted partitioning system automates the placement of code into
333either the main Chrome library or feature-specific .so libraries. Feature code
334may continue to make use of core Chrome code (eg. base::) without modification,
335but Chrome must call feature code through a virtual interface.
336
337Partitioning is explained in [Android Native
338Libraries](android_native_libraries.md#partitioned-libraries).
339
340#### Creating an interface to feature code
341
342One way of creating an interface to a feature library is through an interface
343definition. Feature Foo could define the following in
344`//chrome/android/features/foo/public/foo_interface.h`:
345
346```c++
347class FooInterface {
348 public:
349 virtual ~FooInterface() = default;
350
351 virtual void ProcessInput(const std::string& input) = 0;
352}
353```
354
355Alongside the interface definition, also in
356`//chrome/android/features/foo/public/foo_interface.h`, it's helpful to define a
357factory function type that can be used to create a Foo instance:
358
359```c++
360typedef FooInterface* CreateFooFunction(bool arg1, bool arg2);
361```
362
363<!--- TODO(cjgrant): Add a full, pastable Foo implementation. -->
364The feature library implements class Foo, hiding its implementation within the
365library. The library may then expose a single entrypoint, a Foo factory
366function. Here, C naming is (optionally) used so that the entrypoint symbol
367isn't mangled. In `//chrome/android/features/foo/internal/foo.cc`:
368
369```c++
370extern "C" {
371// This symbol is retrieved from the Foo feature module library via dlsym(),
372// where it's bare address is type-cast to its actual type and executed.
373// The forward declaration here ensures that CreateFoo()'s signature is correct.
374CreateFooFunction CreateFoo;
375
376__attribute__((visibility("default"))) FooInterface* CreateFoo(
377 bool arg1, bool arg2) {
378 return new Foo(arg1, arg2);
379}
380} // extern "C"
381```
382
383Ideally, the interface to the feature will avoid feature-specific types. If a
384feature defines complex data types, and uses them in its own interface, then its
385likely the main library will utilize the code backing these types. That code,
386and anything it references, will in turn be pulled back into the main library.
387
388Therefore, designing the feature inferface to use C types, C++ standard types,
389or classes that aren't expected to move out of Chrome's main library is ideal.
390If feature-specific classes are needed, they simply need to avoid referencing
391feature library internals.
392
393*** note
394**Note:** To help enforce separation between the feature interface and
395implementation, the interface class is best placed in its own GN target, on
396which the feature and main library code both depend.
397***
398
399#### Marking feature entrypoints
400
401Foo's feature module descriptor needs to pull in the appropriate native GN code
402dependencies, and also indicate the name of the file that lists the entrypoint
403symbols. In `//chrome/android/features/foo/foo_module.gni`:
404
405```gn
406foo_module_desc = {
407 ...
408 native_deps = [ "//chrome/android/features/foo/internal:foo" ]
409 native_entrypoints = "//chrome/android/features/foo/internal/module_entrypoints.lst"
410}
411```
412
413The module entrypoint file is a text file listing symbols. In this example,
414`//chrome/android/features/foo/internal/module_entrypoints.lst` has only a
415single factory function exposed:
416
417```shell
418# This file lists entrypoints exported from the Foo native feature library.
419
420CreateFoo
421```
422
423These symbols will be pulled into a version script for the linker, indicating
424that they should be exported in the dynamic symbol table of the feature library.
425
426*** note
427**Note:** If C++ symbol names are chosen as entrypoints, the full mangled names
428must be listed.
429***
430
431Additionally, it's necessary to map entrypoints to a particular partition. To
432follow compiler/linker convention, this is done at the compiler stage. A cflag
433is applied to source file(s) that may supply entrypoints (it's okay to apply the
434flag to all feature source - the attribute is utilized only on modules that
435export symbols). In `//chrome/android/features/foo/internal/BUILD.gn`:
436
437```gn
438static_library("foo") {
439 sources = [
440 ...
441 ]
442
443 # Mark symbols in this target as belonging to the Foo library partition. Only
444 # exported symbols (entrypoints) are affected, and only if this build supports
445 # native modules.
446 if (use_native_modules) {
447 cflags = [ "-fsymbol-partition=libfoo.so" ]
448 }
449}
450```
451
452Feature code is free to use any existing Chrome code (eg. logging, base::,
453skia::, cc::, etc), as well as other feature targets. From a GN build config
454perspective, the dependencies are defined as they normally would. The
455partitioning operation works independently of GN's dependency tree.
456
457```gn
458static_library("foo") {
459 ...
460
461 # It's fine to depend on base:: and other Chrome code.
462 deps = [
463 "//base",
464 "//cc/animation",
465 ...
466 ]
467
468 # Also fine to depend on other feature sub-targets.
469 deps += [
470 ":some_other_foo_target"
471 ]
472
473 # And fine to depend on the interface.
474 deps += [
475 ":foo_interface"
476 ]
477}
478```
479
480#### Opening the feature library
481
482Now, code in the main library can open the feature library and create an
483instance of feature Foo. Note that in this example, no care is taken to scope
484the lifetime of the opened library. Depending on the feature, it may be
485preferable to open and close the library as a feature is used.
486`//chrome/android/features/foo/factory/foo_factory.cc` may contain this:
487
488```c++
489std::unique_ptr<FooInterface> FooFactory(bool arg1, bool arg2) {
490 // Open the feature library, using the partition library helper to map it into
491 // the correct memory location. Specifying partition name *foo* will open
492 // libfoo.so.
493 void* foo_library_handle =
494 base::android::BundleUtils::DlOpenModuleLibraryPartition("foo");
495 }
496 DCHECK(foo_library_handle != nullptr) << "Could not open foo library:"
497 << dlerror();
498
499 // Pull the Foo factory function out of the library. The function name isn't
500 // mangled because it was extern "C".
501 CreateFooFunction* create_foo = reinterpret_cast<CreateFooFunction*>(
502 dlsym(foo_library_handle, "CreateFoo"));
503 DCHECK(create_foo != nullptr);
504
505 // Make and return a Foo!
506 return base::WrapUnique(create_foo(arg1, arg2));
507}
508
509```
510
511*** note
512**Note:** Component builds do not support partitioned libraries (code splitting
513happens across component boundaries instead). As such, an alternate, simplified
514feature factory implementation must be supplied (either by linking in a
515different factory source file, or using #defines in the factory) that simply
516instantiates a Foo object directly.
517***
518
519Finally, the main library is free to utilize Foo:
520
521```c++
522 auto foo = FooFactory::Create(arg1, arg2);
523 foo->ProcessInput(const std::string& input);
524```
525
526### Adding Android resources
Tibor Goldschwendt19364ba2019-04-10 15:59:55527
528In this section we will add the required build targets to add Android resources
529to the Foo DFM.
530
Tibor Goldschwendt68c5f722019-08-01 15:10:15531First, add a resources target to
532`//chrome/android/features/foo/internal/BUILD.gn` and add it as a dependency on
533Foo's `java` target in the same file:
Tibor Goldschwendt19364ba2019-04-10 15:59:55534
535```gn
536...
537android_resources("java_resources") {
538 # Define like ordinary Android resources target.
539 ...
540 custom_package = "org.chromium.chrome.features.foo"
541}
542...
543android_library("java") {
544 ...
545 deps = [
546 ":java_resources",
547 ]
548}
549```
550
551To add strings follow steps
552[here](http://dev.chromium.org/developers/design-documents/ui-localization) to
553add new Java GRD file. Then create
Tibor Goldschwendt68c5f722019-08-01 15:10:15554`//chrome/android/features/foo/internal/java/strings/android_foo_strings.grd` as
555follows:
Tibor Goldschwendt19364ba2019-04-10 15:59:55556
557```xml
558<?xml version="1.0" encoding="UTF-8"?>
559<grit
560 current_release="1"
561 latest_public_release="0"
562 output_all_resource_defines="false">
563 <outputs>
564 <output
565 filename="values-am/android_foo_strings.xml"
566 lang="am"
567 type="android" />
568 <!-- List output file for all other supported languages. See
569 //chrome/android/java/strings/android_chrome_strings.grd for the full
570 list. -->
571 ...
572 </outputs>
573 <translations>
574 <file lang="am" path="vr_translations/android_foo_strings_am.xtb" />
575 <!-- Here, too, list XTB files for all other supported languages. -->
576 ...
577 </translations>
578 <release allow_pseudo="false" seq="1">
579 <messages fallback_to_english="true">
580 <message name="IDS_BAR_IMPL_TEXT" desc="Magical string.">
581 impl
582 </message>
583 </messages>
584 </release>
585</grit>
586```
587
588Then, create a new GRD target and add it as a dependency on `java_resources` in
Tibor Goldschwendt68c5f722019-08-01 15:10:15589`//chrome/android/features/foo/internal/BUILD.gn`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55590
591```gn
592...
593java_strings_grd("java_strings_grd") {
594 defines = chrome_grit_defines
595 grd_file = "java/strings/android_foo_strings.grd"
596 outputs = [
597 "values-am/android_foo_strings.xml",
598 # Here, too, list output files for other supported languages.
599 ...
600 ]
601}
602...
603android_resources("java_resources") {
604 ...
605 deps = [":java_strings_grd"]
606 custom_package = "org.chromium.chrome.features.foo"
607}
608...
609```
610
611You can then access Foo's resources using the
612`org.chromium.chrome.features.foo.R` class. To do this change
Tibor Goldschwendt68c5f722019-08-01 15:10:15613`//chrome/android/features/foo/internal/java/src/org/chromium/chrome/features/foo/FooImpl.java`
Tibor Goldschwendt19364ba2019-04-10 15:59:55614to:
615
616```java
617package org.chromium.chrome.features.foo;
618
619import org.chromium.base.ContextUtils;
620import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30621import org.chromium.base.annotations.UsedByReflection;
Tibor Goldschwendt19364ba2019-04-10 15:59:55622import org.chromium.chrome.features.foo.R;
623
Tibor Goldschwendt573cf3022019-05-10 17:23:30624@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55625public class FooImpl implements Foo {
626 @Override
627 public void bar() {
628 Log.i("FOO", ContextUtils.getApplicationContext().getString(
629 R.string.bar_impl_text));
630 }
631}
632```
633
634*** note
635**Warning:** While your module is emulated (see [below](#on-demand-install))
636your resources are only available through
637`ContextUtils.getApplicationContext()`. Not through activities, etc. We
638therefore recommend that you only access DFM resources this way. See
639[crbug/949729](https://bugs.chromium.org/p/chromium/issues/detail?id=949729)
640for progress on making this more robust.
641***
642
643
644### Module install
645
646So far, we have installed the Foo DFM as a true split (`-m foo` option on the
647install script). In production, however, we have to explicitly install the Foo
Tibor Goldschwendt68c5f722019-08-01 15:10:15648DFM for users to get it. There are three install options: _on-demand_,
649_deferred_ and _conditional_.
Tibor Goldschwendt19364ba2019-04-10 15:59:55650
651
652#### On-demand install
653
654On-demand requesting a module will try to download and install the
655module as soon as possible regardless of whether the user is on a metered
656connection or whether they have turned updates off in the Play Store app.
657
Tibor Goldschwendt573cf3022019-05-10 17:23:30658You can use the autogenerated module class to on-demand install the module like
659so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55660
661```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30662FooModule.install((success) -> {
663 if (success) {
664 FooModule.getImpl().bar();
665 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55666});
667```
668
669**Optionally**, you can show UI telling the user about the install flow. For
Tibor Goldschwendt573cf3022019-05-10 17:23:30670this, add a function like the one below. Note, it is possible
Tibor Goldschwendt19364ba2019-04-10 15:59:55671to only show either one of the install, failure and success UI or any
672combination of the three.
673
674```java
675public static void installModuleWithUi(
676 Tab tab, OnModuleInstallFinishedListener onFinishedListener) {
677 ModuleInstallUi ui =
678 new ModuleInstallUi(
679 tab,
680 R.string.foo_module_title,
681 new ModuleInstallUi.FailureUiListener() {
682 @Override
Samuel Huangfebcccd2019-08-21 20:48:47683 public void onFailureUiResponse(retry) {
684 if (retry) {
685 installModuleWithUi(tab, onFinishedListener);
686 } else {
687 onFinishedListener.onFinished(false);
688 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55689 }
690 });
691 // At the time of writing, shows toast informing user about install start.
692 ui.showInstallStartUi();
Tibor Goldschwendt573cf3022019-05-10 17:23:30693 FooModule.install(
Tibor Goldschwendt19364ba2019-04-10 15:59:55694 (success) -> {
695 if (!success) {
696 // At the time of writing, shows infobar allowing user
697 // to retry install.
698 ui.showInstallFailureUi();
699 return;
700 }
701 // At the time of writing, shows toast informing user about
702 // install success.
703 ui.showInstallSuccessUi();
704 onFinishedListener.onFinished(true);
705 });
706}
707```
708
709To test on-demand install, "fake-install" the DFM. It's fake because
710the DFM is not installed as a true split. Instead it will be emulated by Chrome.
711Fake-install and launch Chrome with the following command:
712
713```shell
714$ $OUTDIR/bin/monochrome_public_bundle install -m base -f foo
Samuel Huang39c7db632019-05-15 14:57:18715$ $OUTDIR/bin/monochrome_public_bundle launch --args="--fake-feature-module-install"
Tibor Goldschwendt19364ba2019-04-10 15:59:55716```
717
718When running the install code, the Foo DFM module will be emulated.
719This will be the case in production right after installing the module. Emulation
720will last until Play Store has a chance to install your module as a true split.
721This usually takes about a day.
722
723*** note
724**Warning:** There are subtle differences between emulating a module and
725installing it as a true split. We therefore recommend that you always test both
726install methods.
727***
728
729
730#### Deferred install
731
732Deferred install means that the DFM is installed in the background when the
733device is on an unmetered connection and charging. The DFM will only be
734available after Chrome restarts. When deferred installing a module it will
735not be faked installed.
736
737To defer install Foo do the following:
738
739```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30740FooModule.installDeferred();
Tibor Goldschwendt19364ba2019-04-10 15:59:55741```
742
Tibor Goldschwendt68c5f722019-08-01 15:10:15743#### Conditional install
744
745Conditional install means the DFM will be installed automatically upon first
746installing or updating Chrome if the device supports a particular feature.
747Conditional install is configured in the module's manifest. To install your
748module on all Daydream-ready devices for instance, your
749`//chrome/android/features/foo/internal/java/AndroidManifest.xml` should look
750like this:
751
752```xml
753<?xml version="1.0" encoding="utf-8"?>
754<manifest xmlns:android="http://schemas.android.com/apk/res/android"
755 xmlns:dist="http://schemas.android.com/apk/distribution"
756 featureSplit="foo">
757
758 <dist:module
759 dist:instant="false"
760 dist:title="@string/foo_module_title">
Ben Mason932dfd12019-08-28 02:19:27761 <dist:fusing dist:include="false" />
Tibor Goldschwendt68c5f722019-08-01 15:10:15762 <dist:delivery>
763 <dist:install-time>
764 <dist:conditions>
765 <dist:device-feature
766 dist:name="android.hardware.vr.high_performance" />
767 </dist:conditions>
768 </dist:install-time>
769 <!-- Allows on-demand or deferred install on non-Daydream-ready
770 devices. -->
771 <dist:on-demand />
772 </dist:delivery>
773 </dist:module>
774
775 <application />
776</manifest>
777```
778
Tibor Goldschwendt19364ba2019-04-10 15:59:55779
780### Integration test APK and Android K support
781
782On Android K we still ship an APK. To make the Foo feature available on Android
783K add its code to the APK build. For this, add the `java` target to
784the `chrome_public_common_apk_or_module_tmpl` in
785`//chrome/android/chrome_public_apk_tmpl.gni` like so:
786
787```gn
788template("chrome_public_common_apk_or_module_tmpl") {
789 ...
790 target(_target_type, target_name) {
791 ...
792 if (_target_type != "android_app_bundle_module") {
793 deps += [
Tibor Goldschwendt68c5f722019-08-01 15:10:15794 "//chrome/android/features/foo/internal:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55795 ]
796 }
797 }
798}
799```
800
801This will also add Foo's Java to the integration test APK. You may also have to
802add `java` as a dependency of `chrome_test_java` if you want to call into Foo
803from test code.