blob: 913d5c9f029278c88db761662eddb46b9e242008 [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
Eric Stevenson8c9ab26b2019-08-30 15:44:40526#### JNI
527
528Read the `jni_generator` [docs](../base/android/jni_generator/README.md) before
529reading this section.
530
531There are some subtleties to how JNI registration works with DFMs:
532
533* Generated wrapper `ClassNameJni` classes are packaged into the DFM's dex file
534* The class containing the actual native definitions, `GEN_JNI.java`, is always
535 stored in the base module
536* If the DFM is only included in bundles that use
537 [implicit JNI registration](android_native_libraries.md#JNI-Native-Methods-Resolution)
538 (i.e. Monochrome and newer), then no extra consideration is necessary
539* Otherwise, the DFM will need to provide a `generate_jni_registration` target
540 that will generate all of the native registration functions
541
542
Christopher Grant8fea5a12019-07-31 19:12:31543### Adding Android resources
Tibor Goldschwendt19364ba2019-04-10 15:59:55544
545In this section we will add the required build targets to add Android resources
546to the Foo DFM.
547
Tibor Goldschwendt68c5f722019-08-01 15:10:15548First, add a resources target to
549`//chrome/android/features/foo/internal/BUILD.gn` and add it as a dependency on
550Foo's `java` target in the same file:
Tibor Goldschwendt19364ba2019-04-10 15:59:55551
552```gn
553...
554android_resources("java_resources") {
555 # Define like ordinary Android resources target.
556 ...
557 custom_package = "org.chromium.chrome.features.foo"
558}
559...
560android_library("java") {
561 ...
562 deps = [
563 ":java_resources",
564 ]
565}
566```
567
568To add strings follow steps
569[here](http://dev.chromium.org/developers/design-documents/ui-localization) to
570add new Java GRD file. Then create
Tibor Goldschwendt68c5f722019-08-01 15:10:15571`//chrome/android/features/foo/internal/java/strings/android_foo_strings.grd` as
572follows:
Tibor Goldschwendt19364ba2019-04-10 15:59:55573
574```xml
575<?xml version="1.0" encoding="UTF-8"?>
576<grit
577 current_release="1"
578 latest_public_release="0"
579 output_all_resource_defines="false">
580 <outputs>
581 <output
582 filename="values-am/android_foo_strings.xml"
583 lang="am"
584 type="android" />
585 <!-- List output file for all other supported languages. See
586 //chrome/android/java/strings/android_chrome_strings.grd for the full
587 list. -->
588 ...
589 </outputs>
590 <translations>
591 <file lang="am" path="vr_translations/android_foo_strings_am.xtb" />
592 <!-- Here, too, list XTB files for all other supported languages. -->
593 ...
594 </translations>
595 <release allow_pseudo="false" seq="1">
596 <messages fallback_to_english="true">
597 <message name="IDS_BAR_IMPL_TEXT" desc="Magical string.">
598 impl
599 </message>
600 </messages>
601 </release>
602</grit>
603```
604
605Then, create a new GRD target and add it as a dependency on `java_resources` in
Tibor Goldschwendt68c5f722019-08-01 15:10:15606`//chrome/android/features/foo/internal/BUILD.gn`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55607
608```gn
609...
610java_strings_grd("java_strings_grd") {
611 defines = chrome_grit_defines
612 grd_file = "java/strings/android_foo_strings.grd"
613 outputs = [
614 "values-am/android_foo_strings.xml",
615 # Here, too, list output files for other supported languages.
616 ...
617 ]
618}
619...
620android_resources("java_resources") {
621 ...
622 deps = [":java_strings_grd"]
623 custom_package = "org.chromium.chrome.features.foo"
624}
625...
626```
627
628You can then access Foo's resources using the
629`org.chromium.chrome.features.foo.R` class. To do this change
Tibor Goldschwendt68c5f722019-08-01 15:10:15630`//chrome/android/features/foo/internal/java/src/org/chromium/chrome/features/foo/FooImpl.java`
Tibor Goldschwendt19364ba2019-04-10 15:59:55631to:
632
633```java
634package org.chromium.chrome.features.foo;
635
636import org.chromium.base.ContextUtils;
637import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30638import org.chromium.base.annotations.UsedByReflection;
Tibor Goldschwendt19364ba2019-04-10 15:59:55639import org.chromium.chrome.features.foo.R;
640
Tibor Goldschwendt573cf3022019-05-10 17:23:30641@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55642public class FooImpl implements Foo {
643 @Override
644 public void bar() {
645 Log.i("FOO", ContextUtils.getApplicationContext().getString(
646 R.string.bar_impl_text));
647 }
648}
649```
650
651*** note
652**Warning:** While your module is emulated (see [below](#on-demand-install))
653your resources are only available through
654`ContextUtils.getApplicationContext()`. Not through activities, etc. We
655therefore recommend that you only access DFM resources this way. See
656[crbug/949729](https://bugs.chromium.org/p/chromium/issues/detail?id=949729)
657for progress on making this more robust.
658***
659
660
661### Module install
662
663So far, we have installed the Foo DFM as a true split (`-m foo` option on the
664install script). In production, however, we have to explicitly install the Foo
Tibor Goldschwendt68c5f722019-08-01 15:10:15665DFM for users to get it. There are three install options: _on-demand_,
666_deferred_ and _conditional_.
Tibor Goldschwendt19364ba2019-04-10 15:59:55667
668
669#### On-demand install
670
671On-demand requesting a module will try to download and install the
672module as soon as possible regardless of whether the user is on a metered
673connection or whether they have turned updates off in the Play Store app.
674
Tibor Goldschwendt573cf3022019-05-10 17:23:30675You can use the autogenerated module class to on-demand install the module like
676so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55677
678```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30679FooModule.install((success) -> {
680 if (success) {
681 FooModule.getImpl().bar();
682 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55683});
684```
685
686**Optionally**, you can show UI telling the user about the install flow. For
Tibor Goldschwendt573cf3022019-05-10 17:23:30687this, add a function like the one below. Note, it is possible
Tibor Goldschwendt19364ba2019-04-10 15:59:55688to only show either one of the install, failure and success UI or any
689combination of the three.
690
691```java
692public static void installModuleWithUi(
693 Tab tab, OnModuleInstallFinishedListener onFinishedListener) {
694 ModuleInstallUi ui =
695 new ModuleInstallUi(
696 tab,
697 R.string.foo_module_title,
698 new ModuleInstallUi.FailureUiListener() {
699 @Override
Samuel Huangfebcccd2019-08-21 20:48:47700 public void onFailureUiResponse(retry) {
701 if (retry) {
702 installModuleWithUi(tab, onFinishedListener);
703 } else {
704 onFinishedListener.onFinished(false);
705 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55706 }
707 });
708 // At the time of writing, shows toast informing user about install start.
709 ui.showInstallStartUi();
Tibor Goldschwendt573cf3022019-05-10 17:23:30710 FooModule.install(
Tibor Goldschwendt19364ba2019-04-10 15:59:55711 (success) -> {
712 if (!success) {
713 // At the time of writing, shows infobar allowing user
714 // to retry install.
715 ui.showInstallFailureUi();
716 return;
717 }
718 // At the time of writing, shows toast informing user about
719 // install success.
720 ui.showInstallSuccessUi();
721 onFinishedListener.onFinished(true);
722 });
723}
724```
725
726To test on-demand install, "fake-install" the DFM. It's fake because
727the DFM is not installed as a true split. Instead it will be emulated by Chrome.
728Fake-install and launch Chrome with the following command:
729
730```shell
731$ $OUTDIR/bin/monochrome_public_bundle install -m base -f foo
Samuel Huang39c7db632019-05-15 14:57:18732$ $OUTDIR/bin/monochrome_public_bundle launch --args="--fake-feature-module-install"
Tibor Goldschwendt19364ba2019-04-10 15:59:55733```
734
735When running the install code, the Foo DFM module will be emulated.
736This will be the case in production right after installing the module. Emulation
737will last until Play Store has a chance to install your module as a true split.
738This usually takes about a day.
739
740*** note
741**Warning:** There are subtle differences between emulating a module and
742installing it as a true split. We therefore recommend that you always test both
743install methods.
744***
745
746
747#### Deferred install
748
749Deferred install means that the DFM is installed in the background when the
750device is on an unmetered connection and charging. The DFM will only be
751available after Chrome restarts. When deferred installing a module it will
752not be faked installed.
753
754To defer install Foo do the following:
755
756```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30757FooModule.installDeferred();
Tibor Goldschwendt19364ba2019-04-10 15:59:55758```
759
Tibor Goldschwendt68c5f722019-08-01 15:10:15760#### Conditional install
761
762Conditional install means the DFM will be installed automatically upon first
763installing or updating Chrome if the device supports a particular feature.
764Conditional install is configured in the module's manifest. To install your
765module on all Daydream-ready devices for instance, your
766`//chrome/android/features/foo/internal/java/AndroidManifest.xml` should look
767like this:
768
769```xml
770<?xml version="1.0" encoding="utf-8"?>
771<manifest xmlns:android="http://schemas.android.com/apk/res/android"
772 xmlns:dist="http://schemas.android.com/apk/distribution"
773 featureSplit="foo">
774
775 <dist:module
776 dist:instant="false"
777 dist:title="@string/foo_module_title">
Ben Mason932dfd12019-08-28 02:19:27778 <dist:fusing dist:include="false" />
Tibor Goldschwendt68c5f722019-08-01 15:10:15779 <dist:delivery>
780 <dist:install-time>
781 <dist:conditions>
782 <dist:device-feature
783 dist:name="android.hardware.vr.high_performance" />
784 </dist:conditions>
785 </dist:install-time>
786 <!-- Allows on-demand or deferred install on non-Daydream-ready
787 devices. -->
788 <dist:on-demand />
789 </dist:delivery>
790 </dist:module>
791
792 <application />
793</manifest>
794```
795
Tibor Goldschwendt19364ba2019-04-10 15:59:55796
797### Integration test APK and Android K support
798
799On Android K we still ship an APK. To make the Foo feature available on Android
800K add its code to the APK build. For this, add the `java` target to
801the `chrome_public_common_apk_or_module_tmpl` in
802`//chrome/android/chrome_public_apk_tmpl.gni` like so:
803
804```gn
805template("chrome_public_common_apk_or_module_tmpl") {
806 ...
807 target(_target_type, target_name) {
808 ...
809 if (_target_type != "android_app_bundle_module") {
810 deps += [
Tibor Goldschwendt68c5f722019-08-01 15:10:15811 "//chrome/android/features/foo/internal:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55812 ]
813 }
814 }
815}
816```
817
818This will also add Foo's Java to the integration test APK. You may also have to
819add `java` as a dependency of `chrome_test_java` if you want to call into Foo
820from test code.