Like desktop, mobile Firefox supplies a way for partners to repackage a set of customization files into a Firefox build without recompiling. We don't want to make custom binary builds that need to be QA'd extensively. The "distribution files" concept allows a small set of external files to be used to customize a standard Firefox build. Things that can be customized include:
- Default bookmarks
- Preferences
- Lightweight themes
- Search engines
- Add-ons
- Quickshare defaults
- Suggested sites
A sample distribution directory is available on github.
Contents
Overview
There are currently three ways distribution files can be ... distributed:
- APK distributions: A folder of distribution files is repackaged into the Android APK. During the first run, those files are extracted from the APK and stored in the
/data/data/org.mozilla.firefox/distribution/*
directory. - System distributions: A folder of distribution files are shipped with the ROM image in
/system/org.mozilla.firefox/distribution/*
directory. Shipping in this way ensures the files are not easily overwritten or removed by an application update. Note, files found in the/data/data/org.mozilla.firefox/distribution/*
directory will "win" in cases where both locations are used. - Over-the-air distributions: A distribution zip file is downloaded over-the-air as a result of a post-install referrer intent. See bug 1013024. This feature should ship with Firefox 33.
Distributions are processed asynchronously during first run. Frontend code can chain post-distribution tasks via Distribution.addOnDistributionReadyCallback
.
Each type of data has its own nuances:
- Default bookmarks are read from the APK directly or from the "distribution" folder (if it already exists) any time a profile is created. These bookmarks are created prior to Firefox's default bookmarks.
- Preferences are set as "default" preferences on each run, since a "default" preference only lasts for the lifetime of the session. They are created as "default" preferences to allow "user" preferences to override them.
- Lightweight themes are actually just preferences. The data for the theme must live online somewhere. Typical places are the AMO theme or getpersonas.com sites.
- Search engine plugins and add-ons must follow a fixed folder layout.
- If distribution data is processed after the initial quickshare set is loaded, these options won't take effect until next run (bug 1021176).
Bookmarks
Default bookmarks are specified in /distribution/bookmarks.json. This file should contain an array of bookmark JSON objects with the following properties:
- title: (required) string for the bookmark title
- url: (required) string for the bookmark URL
- title.<locale>: (optional) title string for the given locale
- url.<locale>: (optional) URL string for the given locale
- icon: (optional) data URI for the bookmark favicon
- pinned: (optional) boolean for whether or not to pin this bookmark to the about:home top sites
- NOTE: If a bookmark is pinned, it is not visible in the Bookmarks list. Pinning only affects Tops Sites.
Here is an example bookmarks.json:
[ { "title": "Queso", "url": "http://queso.com/", "title.en_US": "Cheese", "url.en_US": "http://cheese.com", "icon": "data:image/png;base64,..." } ]
Preferences
Preferences are specified in /distribution/preferences.json. This file should contain a JSON object with the following properties:
- Global
- id: (required) a short string unique to this distribution
- version: (required) version of the distribution (not the version of Firefox)
- about: (required) a short descriptive (ui-visible) string for this distribution
- about.<locale>: (optional) descriptive string for the given locale
- Preferences (optional)
- This sets default Gecko preferences for the profile (i.e. prefs that would appear in about:config)
- LocalizablePreferences (optional)
- LocalizablePreferences.<locale> (optional)
- ApplicationPreferences (optional)
- This sets as the app-scoped SharedPreferences for the entire application. Since GeckoPreferenceFragment only accept application-scoped sharedPreferences, this object will let partner also have the ability to override them.
- AndroidPreferences (optional)
- This sets Android SharedPreferences for the profile
Here is an example preferences.json:
{ "Global": { "id": "testpartner", "version": 1.0, "about": "Afiliado de Prueba", "about.en-US": "Test Partner" }, "Preferences": { "distribution.test.string": "a string pref", "distribution.test.boolean": true, "distribution.test.int": 5 }, "LocalizablePreferences": { "distribution.test.localizeable": "http://test.org/%LOCALE%/%LOCALE%/", "distribution.test.localizeable-override": "http://test.org/%LOCALE%/%LOCALE%/" }, "LocalizablePreferences.en-US": { "distribution.test.localizeable-override": "http://cheese.com" }, "ApplicationPreferences": { "newtab.load_homepage": "true" }, "AndroidPreferences": { "homepage": "http://www.mozilla.com" } }
Lightweight Themes
Lightweight themes are specified in the "Preferences" section of /distribution/preferences.json. Here is an example of the lightweight theme preferences needed for this to work:
"Preferences": { "lightweightThemes.isThemeSelected": true, "lightweightThemes.persisted.footerURL": false, "lightweightThemes.persisted.headerURL": false, "lightweightThemes.usedThemes": "[{\"id\":\"491717\",\"name\":\"colored trees\",\"headerURL\":\"http://getpersonas-cdn.mozilla.net/static/1/7/491717/trees.png?1358076678\",\"footerURL\":\"http://getpersonas-cdn.mozilla.net/static/1/7/491717/trees-base.png?1358076678\",\"textcolor\":\"#1d022b\",\"accentcolor\":\"#000000\",\"iconURL\":\"http://getpersonas-cdn.mozilla.net/static/1/7/491717/preview_small.jpg?1358076678\",\"previewURL\":\"http://getpersonas-cdn.mozilla.net/static/1/7/491717/preview.jpg?1358076678\",\"author\":\"birdyann\",\"description\":\"trees arbres colored colors couleurs\",\"updateURL\":\"https://www.getpersonas.com/en-US/update_check/491717\",\"version\":\"1358076678\",\"updateDate\":1359579038207,\"installDate\":1359579038207}]" }
Search Plugins
Include search plugins in a directory structure like the following, and they will be automatically loaded:
* distribution/ * searchplugins/ * common/ * <plugin.xml> * <plugin.xml> * ... * locale/ * <locale>/ * <plugin.xml> * <plugin.xml> * ... * <locale>/ * <plugin.xml> * <plugin.xml> * ...
If a directory for the locale the browser is running in doesn't exist, Firefox will automatically try the 'default' locale, as defined by the distribution.searchplugins.defaultLocale
preference.
To set a given search engine as the default, you need to use LocalizablePreferences
to override the browser.search.defaultenginename
preference. You should also override the browser.search.order.1
preference to put the default engine in the right sort order. For example:
{ ... "LocalizablePreferences": { "browser.search.defaultenginename": "<name-of-engine>", "browser.search.order.1": "<name-of-engine>" ... } ... }
Add-ons
Include add-ons in an extensions directory as follows, and they will be automatically loaded:
* distribution/ * extensions/ * someaddon@somedomain.com.xpi * ...
Be sure to name the add-on XPI with the ID that's specified in the add-on's install.rdf.
Note: As of Firefox 41, we require all add-ons to be signed through AMO, so you should make sure this XPI is signed properly before shipping in a distribution.
Add a file named history.xml
under distribution/quickshare
. Its contents should be something like:
<historical-records> <historical-record activity="com.android.mms/com.android.mms.ui.ConversationComposer" time="0" weight="1"/> </historical-records>
Suggested sites
Added by bug 1012462 in Firefox 33.
Use a directory tree like the following. Locales are named via a locale code, such as en-US
.
* distribution/ * suggestedsites/ * locales/ * <locale>/ * suggestedsites.json * res/ * hdpi/ * <distro>.png * xxhdpi/ * <distro>.png
The suggestedsites.json file should be a JSON array:
[ { url: "<site-url>", title: "<site-title>", imageurl: "gecko.distribution://<image-dir>/<image-name>", bgcolor: "<color-hex-code>", /* "#FF00FF" with leading '#' */ trackingid: <tiles-id> /* optional integer */ }, ... ]
Where <image-dir>
is the directory containing the density-specific image directories (mdpi
, hdpi
, etc) and <image-name>
is the filename under the density-specific directories without the file extension. For example, gecko.distribution://suggestedsites/res/distroimage
image URL will be translated to <distribution-root-dir>/suggestedsites/res/xhdpi/distroimage.png
on a XHDPI device.
Ideally, the title of your suggested site should be short so that it only displays on one line and does not cover too much of the suggested site icon. That being said, there is a maximum of 2 lines for distribution suggested site titles. Multiple suggested sites should not begin with the same text.
The list of suggested sites from the distribution will be prepended to the default list of suggested sites that ships with Firefox.
Image Specs
When creating images for use in Suggested Sites, please use the following guidelines. Make sure you provide images for all the DPI levels or the thumbnail may not look good on some devices:
Images Sizes (Bounds) - Firefox 57 and above
- xxhdpi: 270x270
- xhdpi: 180x180
- hdpi: 135x135
Images Sizes (Bounds) - Firefox 56 and below
- xxhdpi: 375x195
- xhdpi: 250x130
- hdpi: 188x98
- mdpi:125x65
Testing a distribution locally
Note: you need a clean install. If you have already run Firefox, go to settings and clear the app's data
APK distribution
- To create a repackaged distribution:
- For Firefox for Android 45 and later:
- Your distribution folder contents should be at:
/path/to/fennec-distribution-sample/assets/distribution
- Add a line like the following to your mozconfig:
ac_add_options --with-android-distribution-directory=/path/to/fennec-distribution-sample
- Make sure you have run
./mach configure
before building and packaging - See bug 1163082 for details.
- Note: By default the distribution is going into the install bouncer APK. To change that set MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER to False in mobile/android/moz.configure (See bug 1258372 for details) or add export MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER= to mozconfig in Firefox 50 or later.
- Your distribution folder contents should be at:
- For Firefox for Android 44 and earlier:
- Put your distribution folder at
objdir/dist/bin/distribution
- Run
./mach package
to package the app
- Put your distribution folder at
- When the app is launched, the distribution files will be copied out to
/data/data/org.mozilla.fennec_yourname/distribution
, and they will override a ROM image distribution if it is present
- For Firefox for Android 45 and later:
System distribution
- To create a ROM image distribution, follow these steps with a rooted phone:
- Copy the distribution files to a writable location
adb shell mkdir /data/local/tmp/distribution adb push distribution /data/local/tmp/distribution
- Run
adb shell
to get and adb shell
- Run
$ su # mount # mount -o rw,remount /system # cp -r /data/local/tmp/distribution /system/<package>/ # (replace <package> with the ID for the package you want to test, e.g. org.mozilla.firefox or org.mozilla.fennec)
Testing distribution download
A distribution download is triggered when two things are true:
- This is the first run of Firefox (or Clear Data has been used).
- An
INSTALL_REFERRER
intent has been received prior to launch.
The Google Play app takes care of sending that intent. You can also manually send it via adb shell
. Assuming you're testing Nightly:
am broadcast -a com.android.vending.INSTALL_REFERRER \ -n org.mozilla.firefox/org.mozilla.gecko.distribution.ReferrerReceiver \ -f 32 \ --es "referrer" "utm_source=mozilla\&utm_content=testsigned\&utm_campaign=distribution"
This will fetch the "testsigned.jar" distribution.
(The -f 32 flag makes sure this intent is sent to stopped packages.)
You can also do this from another Android application:
Intent i = new Intent("com.android.vending.INSTALL_REFERRER"); i.setPackage("org.mozilla.firefox"); i.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); i.putExtra("referrer", "utm_source=mozilla&utm_content=testsigned&utm_campaign=distribution"); getContext().sendBroadcast(i);
You can the below example link to install the testsigned example package:
This must be on a device that does not have Firefox installed.
Telemetry will be recorded for the download: FENNEC_DISTRIBUTION_DOWNLOAD_TIME_MS
records the download time in milliseconds, and FENNEC_DISTRIBUTION_CODE_CATEGORY
records the result. The value will be one of these:
- 0
- Unexpected HTTP status.
- 1
- HTTP 1xx. Should not occur.
- 2
- HTTP 2xx. The expected result.
- 3
- HTTP 3xx. Should not occur.
- 4
- HTTP 4xx. Should not occur.
- 5
- HTTP 5xx. Should not occur.
- 6
- The device was offline.
- 7
- Generic fetch exception.
- 8
- Failure during processing of downloaded zip.
- 9
- Security exception: malformed, corrupted, or modified signatures.
- 10
- Malformed distribution file.
- 11
- Fetch: socket error.
- 12
- Fetch: SSL error.
- 13
- Fetch: non-success HTTP response.
- 14
- Fetch: invalid Content-Type header received.
Crafting a distribution referrer
These are the fields exposed in a referrer URL to Play:
-
utm_content
- The distribution name. Must be unique. Identifies the distribution name.
-
utm_campaign
- The campaign name. Must be
distribution
. -
utm_source
- Original referrer. Must be
mozilla
for both downloadable distributions and campaign tracking. -
utm_medium
- Campaign medium. E.g.,
email
. Not used by Firefox. -
utm_term
- Search terms. E.g.,
web+browser
. Not used by Firefox.
Distribution bundles
Firefox for Android 46.0 introduced support for distribution bundles based on the MCC (mobile country code, [1]) and MNC (mobile network code) codes of the inserted SIM card. Instead of the default distribution directory Firefox will search a distribution in the following sub directories first:
- (distribution directory)/<MCC>/<MNC> - For bundled distributions for specific network providers
- (distribution directory)/<MCC> - For bundled distributions for specific countries
- (distribution directory)/default - For bundled distributions with no matching MCC/MNC
Example distribution bundle
distribution ├── 208 // 208 = Country code of France │ └── bookmarks.json // Bookmarks just for users with a SIM card of a French operator ├── 262 // 262 = Country code of Germany │ └── 01 // 01 = Network code of "Deutsche Telekom" │ ├── bookmarks.json // Bookmarks.. │ └── preferences.json // ... and preferences just for users of a SIM card of "Deutsche Telekom" └── default // Default folder └── preferences.json // Preferences for all users with different SIM cards.
Partner Content Providers
Android system images can ship with two content providers to customize installed browsers:
- content://com.android.partnerbookmarks - For partner bookmarks
- content://com.android.partnerbrowsercustomizations - For customizations (Details see below)
Firefox for Android supports reading data from both of those content providers (Starting with Firefox for Android 50.0) if this has been enabled by a distribution. If the following preferences are not written by a distribution then Firefox for Android will ignore the data from the partner content providers.
/distribution/preferences.json:
[..] "AndroidPreferences": { "distribution.read_partner_bookmarks_provider": true, "distribution.read_partner_customizations_provider": true } [..]
Bookmarks Content Provider
Firefox for Android (>= 50.0) supports reading bookmarks and folders from the content provider. Reading icons from the content provider is currently not supported (See bug 1286203). Editing and deleting bookmarks/folders is not supported yet but planned.
An example implementation of the content provider can be found in the official Android source tree:
Customizations Content Provider
Firefox for Android (>= 50.0) only supports reading the 'homepage' from the content provider. All other values are ignored.
content://com.android.partnerbrowsercustomizations
Path | Column | Supported |
---|---|---|
/homepage | homepage | Yes |
/disableincognitomode | disableincognitomode | No |
/disablebookmarksediting | disablebookmarksediting | No |
Testing
There's a test app with sample data available on GitHub:
The test app uses slightly different content provider authorities to allow being installed on devices that already ship with partner content providers:
- com.android.partnerbookmarks.test
- com.android.partnerbrowsercustomizations.test
A custom Firefox build is required to read from the test content providers.