Simplify build variants to just: debug, nightly, beta and release.
Co-authored-by: Johan Lorenzo <jlorenzo@mozilla.com>master
41
README.md
|
@ -81,10 +81,9 @@ Pre-requisites:
|
||||||
2. **Import** the project into Android Studio **or** build on the command line:
|
2. **Import** the project into Android Studio **or** build on the command line:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./gradlew clean app:assembleGeckoBetaDebug
|
./gradlew clean app:assembleDebug
|
||||||
```
|
```
|
||||||
|
|
||||||
Use app:assembleGeckoNightlyDebug to build with the Gecko Nightly version instead.
|
|
||||||
If this errors out, make sure that you have an `ANDROID_SDK_ROOT` environment
|
If this errors out, make sure that you have an `ANDROID_SDK_ROOT` environment
|
||||||
variable pointing to the right path.
|
variable pointing to the right path.
|
||||||
|
|
||||||
|
@ -94,39 +93,29 @@ Pre-requisites:
|
||||||
We have a lot of build variants. Each variant is composed of two flavors. One flavor is the version of Gecko to use and the other describes
|
We have a lot of build variants. Each variant is composed of two flavors. One flavor is the version of Gecko to use and the other describes
|
||||||
which app id and settings to use. Here is a description of what each means:
|
which app id and settings to use. Here is a description of what each means:
|
||||||
|
|
||||||
- **geckoBeta** (recommended) uses the Beta variant of the Gecko rendering engine, which corresponds to the next version of Gecko which will go to production
|
|
||||||
- **geckoNightly** uses the Nightly variant of the Gecko rendering engine, which is the version which will arrive after beta and is less stable
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
|
|
||||||
- **debug** uses debug symbols and debug signing, adds tools like LeakCanary for troubleshooting, and does not strip unused or wasteful code
|
- **debug** uses debug symbols and debug signing, adds tools like LeakCanary for troubleshooting, and does not strip unused or wasteful code
|
||||||
- **fenixNightly** is a release build with nightly signing which uses the org.mozilla.fenix.nightly app id for nightly releases to Google Play
|
- **nightly** uses the Nightly variant of the Gecko rendering engine, which is the version which will arrive after beta and is less stable
|
||||||
- **fenixBeta** is a release build with beta signing which uses the org.mozilla.fenix.beta app id for beta releases to Google Play
|
- **beta** (recommended) uses the Beta variant of the Gecko rendering engine, which corresponds to the next version of Gecko which will go to production
|
||||||
- **fenixProduction** is a release build with release signing which uses the org.mozilla.fenix app id for production releases to Google Play
|
- **release** is a release build with release signing which uses the org.mozilla.firefox app id for production releases to Google Play
|
||||||
- **fennecProduction** is an experimental build with release signing which uses the org.mozilla.firefox app id and supports upgrading the older Firefox. **WARNING** Pre-production versions of this may result in data loss.
|
|
||||||
- **forPerformanceTest**: see "Performance Build Variants" below.
|
|
||||||
|
|
||||||
#### Performance Build Variants
|
#### Performance Build Variants
|
||||||
For accurate performance measurements, read this section!
|
For accurate performance measurements, read this section!
|
||||||
|
|
||||||
If you want to analyze performance during **local development** (note: there is a non-trivial performance impact - see caveats):
|
If you want to analyze performance during **local development** (note: there is a non-trivial performance impact - see caveats):
|
||||||
- Recommendation: use a `forPerformanceTest` variant with local Leanplum, Adjust, & Sentry API tokens: contact the front-end perf group for access to them
|
- Recommendation: use a debuggable variant (see "local.properties helpers" below) with local Leanplum, Adjust, & Sentry API tokens: contact the front-end perf group for access to them
|
||||||
- Rationale: `forPerformanceTest` is a release variant with `debuggable` set to true. There are numerous performance-impacting differences between debug and release variants so we need a release variant. To profile, we also need debuggable, which is disabled on other release variants. If API tokens are not provided, the SDKs may change their behavior in non-trivial ways.
|
- Rationale: There are numerous performance-impacting differences between debug and release variants so we need a release variant. To profile, we also need debuggable, which is disabled by default for release variants. If API tokens are not provided, the SDKs may change their behavior in non-trivial ways.
|
||||||
- Caveats:
|
- Caveats:
|
||||||
- debuggable has a non-trivial & variable impact on performance but is needed to take profiles.
|
- debuggable has a non-trivial & variable impact on performance but is needed to take profiles.
|
||||||
- Random experiment opt-in & feature flags may impact performance (see [perf-frontend-issues#45](https://github.com/mozilla-mobile/perf-frontend-issues/issues/45) for mitigation).
|
- Random experiment opt-in & feature flags may impact performance (see [perf-frontend-issues#45](https://github.com/mozilla-mobile/perf-frontend-issues/issues/45) for mitigation).
|
||||||
- This is slower to build than debug builds because it does additional tasks (e.g. minification) similar to other release builds
|
- This is slower to build than debug builds because it does additional tasks (e.g. minification) similar to other release builds
|
||||||
|
|
||||||
Nightly `forPerformanceTest` variants with API tokens already added [are also available from Taskcluster](https://firefox-ci-tc.services.mozilla.com/tasks/index/project.mobile.fenix.v2.performance-test/).
|
|
||||||
|
|
||||||
If you want to run **performance tests/benchmarks** in automation or locally:
|
If you want to run **performance tests/benchmarks** in automation or locally:
|
||||||
- Recommendation: production builds. If debuggable is required, use recommendation above but note the caveat above. If your needs are not met, please contact the front-end perf group to identify a new solution.
|
- Recommendation: production builds. If debuggable is required, use recommendation above but note the caveat above. If your needs are not met, please contact the front-end perf group to identify a new solution.
|
||||||
- Rationale: like the rationale above, we need release variants so the choice is based on the debuggable flag.
|
- Rationale: like the rationale above, we need release variants so the choice is based on the debuggable flag.
|
||||||
|
|
||||||
For additional context on these recommendations, see [the perf build variant analysis](https://docs.google.com/document/d/1aW-m0HYncTDDiRz_2x6EjcYkjBpL9SHhhYix13Vil30/edit#).
|
For additional context on these recommendations, see [the perf build variant analysis](https://docs.google.com/document/d/1aW-m0HYncTDDiRz_2x6EjcYkjBpL9SHhhYix13Vil30/edit#).
|
||||||
|
|
||||||
Before you can install any release variants including `forPerformanceTest`, **you will need to sign them:** see [Automatically signing release builds](#automatically-sign-release-builds) for details.
|
Before you can install any release variants, **you will need to sign them:** see [Automatically signing release builds](#automatically-sign-release-builds) for details.
|
||||||
|
|
||||||
## Pre-push hooks
|
## Pre-push hooks
|
||||||
To reduce review turn-around time, we'd like all pushes to run tests locally. We'd
|
To reduce review turn-around time, we'd like all pushes to run tests locally. We'd
|
||||||
|
@ -181,6 +170,22 @@ With this line, release build variants will automatically be signed with your de
|
||||||
|
|
||||||
This is helpful when you're building release variants frequently, for example to test feature flags and or do performance analyses.
|
This is helpful when you're building release variants frequently, for example to test feature flags and or do performance analyses.
|
||||||
|
|
||||||
|
### Building debuggable release variants
|
||||||
|
|
||||||
|
Nightly, Beta and Release variants are getting published to Google Play and therefore are not debuggable. To locally create debuggable builds of those variants, add the following to `<proj-root>/local.properties`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
debuggable
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting raptor manifest flag
|
||||||
|
|
||||||
|
To set the raptor manifest flag in Nightly, Beta and Release variants, add the following to `<proj-root>/local.properties`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
raptorEnabled
|
||||||
|
```
|
||||||
|
|
||||||
### Auto-publication workflow for android-components and application-services
|
### Auto-publication workflow for android-components and application-services
|
||||||
If you're making changes to these projects and want to test them in Fenix, auto-publication workflow is the fastest, most reliable
|
If you're making changes to these projects and want to test them in Fenix, auto-publication workflow is the fastest, most reliable
|
||||||
way to do that.
|
way to do that.
|
||||||
|
|
145
app/build.gradle
|
@ -6,7 +6,6 @@ apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'jacoco'
|
apply plugin: 'jacoco'
|
||||||
apply from: "$project.rootDir/automation/gradle/versionCode.gradle"
|
|
||||||
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
||||||
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
||||||
|
|
||||||
|
@ -48,6 +47,10 @@ android {
|
||||||
if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey")) {
|
if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey")) {
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gradle.hasProperty("localProperties.debuggable")) {
|
||||||
|
debuggable true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -58,25 +61,31 @@ android {
|
||||||
resValue "bool", "IS_DEBUG", "true"
|
resValue "bool", "IS_DEBUG", "true"
|
||||||
pseudoLocalesEnabled true
|
pseudoLocalesEnabled true
|
||||||
}
|
}
|
||||||
forPerformanceTest releaseTemplate >> { // the ">>" concatenates the raptor-specific options with the template
|
nightly releaseTemplate >> {
|
||||||
applicationIdSuffix ".fenix.performancetest"
|
|
||||||
debuggable true
|
|
||||||
manifestPlaceholders = [
|
|
||||||
// Since we configure this build to behave like a "fennec" flavored build, we need
|
|
||||||
// to set a shared user id for the manifest. The actual values does not matter.
|
|
||||||
// However we pick a unique value to not "clash" with other Fenix/Fennec builds
|
|
||||||
// installed on the device.
|
|
||||||
"sharedUserId": "org.mozilla.fenix.performancetest.sharedID"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
fenixProduction releaseTemplate >> {
|
|
||||||
applicationIdSuffix ".fenix"
|
applicationIdSuffix ".fenix"
|
||||||
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
||||||
def deepLinkSchemeValue = "fenix-nightly"
|
def deepLinkSchemeValue = "fenix-nightly"
|
||||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
||||||
manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue]
|
manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue]
|
||||||
}
|
}
|
||||||
fennecProduction releaseTemplate >> {
|
beta releaseTemplate >> {
|
||||||
|
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
||||||
|
applicationIdSuffix ".firefox_beta"
|
||||||
|
def deepLinkSchemeValue = "fenix-beta"
|
||||||
|
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
||||||
|
manifestPlaceholders = [
|
||||||
|
// This release type is meant to replace Firefox (Beta channel) and therefore needs to inherit
|
||||||
|
// its sharedUserId for all eternity. See:
|
||||||
|
// https://searchfox.org/mozilla-central/search?q=moz_android_shared_id&case=false®exp=false&path=
|
||||||
|
// Shipping an app update without sharedUserId can have
|
||||||
|
// fatal consequences. For example see:
|
||||||
|
// - https://issuetracker.google.com/issues/36924841
|
||||||
|
// - https://issuetracker.google.com/issues/36905922
|
||||||
|
"sharedUserId": "org.mozilla.firefox.sharedID",
|
||||||
|
"deepLinkScheme": deepLinkSchemeValue
|
||||||
|
]
|
||||||
|
}
|
||||||
|
release releaseTemplate >> {
|
||||||
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
||||||
applicationIdSuffix ".firefox"
|
applicationIdSuffix ".firefox"
|
||||||
def deepLinkSchemeValue = "fenix"
|
def deepLinkSchemeValue = "fenix"
|
||||||
|
@ -93,49 +102,6 @@ android {
|
||||||
"deepLinkScheme": deepLinkSchemeValue
|
"deepLinkScheme": deepLinkSchemeValue
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
fennecBeta releaseTemplate >> {
|
|
||||||
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
|
|
||||||
applicationIdSuffix ".firefox_beta"
|
|
||||||
def deepLinkSchemeValue = "fenix-beta"
|
|
||||||
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
|
|
||||||
manifestPlaceholders = [
|
|
||||||
// This release type is meant to replace Firefox (Release channel) and therefore needs to inherit
|
|
||||||
// its sharedUserId for all eternity. See:
|
|
||||||
// https://searchfox.org/mozilla-central/search?q=moz_android_shared_id&case=false®exp=false&path=
|
|
||||||
// Shipping an app update without sharedUserId can have
|
|
||||||
// fatal consequences. For example see:
|
|
||||||
// - https://issuetracker.google.com/issues/36924841
|
|
||||||
// - https://issuetracker.google.com/issues/36905922
|
|
||||||
"sharedUserId": "org.mozilla.firefox.sharedID",
|
|
||||||
"deepLinkScheme": deepLinkSchemeValue
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variantFilter { // There's a "release" build type that exists by default that we don't use (it's replaced by "nightly" and "beta")
|
|
||||||
if (buildType.name == 'release') {
|
|
||||||
setIgnore true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current build variant setup:
|
|
||||||
//
|
|
||||||
// | geckoNightly | geckoBeta |
|
|
||||||
// |--------------------|---------------|-----------|
|
|
||||||
// | debug | ✅ | ✅ | Both variants for testing and development.
|
|
||||||
// | forPerformanceTest | ✅ | ✅ | Both variants unless the perf team only cares about Nightly (TBD).
|
|
||||||
// | fenixProduction | ✅ | ❌ | Fenix Production (to be renamed `Nightly`) ships with GV Nightly
|
|
||||||
// | fennecProduction | ❌ | ✅ | Fenix build to replace production Firefox builds
|
|
||||||
// | fennecBeta | ❌ | ✅ | Fenix build to replace beta Firefox builds
|
|
||||||
|
|
||||||
def flavors = flavors*.name.toString().toLowerCase()
|
|
||||||
|
|
||||||
if (buildType.name == 'fenixProduction' && flavors.contains("geckobeta")) {
|
|
||||||
setIgnore true
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((buildType.name == 'fennecProduction' || buildType.name == 'fennecBeta') && flavors.contains("geckonightly")) {
|
|
||||||
setIgnore true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
|
@ -162,30 +128,20 @@ android {
|
||||||
androidTest {
|
androidTest {
|
||||||
resources.srcDirs += ['src/androidTest/resources']
|
resources.srcDirs += ['src/androidTest/resources']
|
||||||
}
|
}
|
||||||
fennecBeta {
|
debug {
|
||||||
java.srcDirs = ['src/migration/java']
|
java.srcDirs = ['src/geckoNightly/java']
|
||||||
|
}
|
||||||
|
nightly {
|
||||||
|
java.srcDirs = ['src/geckoNightly/java']
|
||||||
|
}
|
||||||
|
beta {
|
||||||
|
java.srcDirs = ['src/migration/java', 'src/geckoBeta/java']
|
||||||
manifest.srcFile "src/migration/AndroidManifest.xml"
|
manifest.srcFile "src/migration/AndroidManifest.xml"
|
||||||
}
|
}
|
||||||
fennecProduction {
|
release {
|
||||||
java.srcDirs = ['src/migration/java']
|
java.srcDirs = ['src/migration/java', 'src/geckoRelease/java']
|
||||||
manifest.srcFile "src/migration/AndroidManifest.xml"
|
manifest.srcFile "src/migration/AndroidManifest.xml"
|
||||||
}
|
}
|
||||||
forPerformanceTest {
|
|
||||||
// We want our performance test builds to use the same code as our "fennec" flavor builds
|
|
||||||
// since those builds will ship to our large user base.
|
|
||||||
java.srcDirs = ['src/migration/java']
|
|
||||||
manifest.srcFile "src/migration/AndroidManifest.xml"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
productFlavors {
|
|
||||||
geckoNightly {
|
|
||||||
dimension "engine"
|
|
||||||
}
|
|
||||||
|
|
||||||
geckoBeta {
|
|
||||||
dimension "engine"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
splits {
|
splits {
|
||||||
|
@ -216,8 +172,6 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def baseVersionCode = generatedVersionCode
|
|
||||||
|
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------
|
||||||
|
@ -252,27 +206,11 @@ android.applicationVariants.all { variant ->
|
||||||
// same version code. Therefore we need to have different version codes for our ARM and x86
|
// same version code. Therefore we need to have different version codes for our ARM and x86
|
||||||
// builds.
|
// builds.
|
||||||
|
|
||||||
// Our generated version code now has a length of 9 (See automation/gradle/versionCode.gradle).
|
|
||||||
// Our x86 builds need a higher version code to avoid installing ARM builds on an x86 device
|
|
||||||
// with ARM compatibility mode.
|
|
||||||
|
|
||||||
variant.outputs.each { output ->
|
variant.outputs.each { output ->
|
||||||
def abi = output.getFilter(OutputFile.ABI)
|
def abi = output.getFilter(OutputFile.ABI)
|
||||||
|
// We use the same version code generator, that we inherited from Fennec, across all channels - even on
|
||||||
def versionCodeOverride
|
// channels that never shipped a Fennec build.
|
||||||
if (variant.name.contains("Fennec")) {
|
def versionCodeOverride = Config.generateFennecVersionCode(abi)
|
||||||
versionCodeOverride = Config.generateFennecVersionCode(abi)
|
|
||||||
} else if (abi == "x86_64") {
|
|
||||||
versionCodeOverride = baseVersionCode + 3
|
|
||||||
} else if (abi == "x86") {
|
|
||||||
versionCodeOverride = baseVersionCode + 2
|
|
||||||
} else if (abi == "arm64-v8a") {
|
|
||||||
versionCodeOverride = baseVersionCode + 1
|
|
||||||
} else if (abi == "armeabi-v7a") {
|
|
||||||
versionCodeOverride = baseVersionCode
|
|
||||||
} else {
|
|
||||||
throw RuntimeException("Unknown ABI: $abi")
|
|
||||||
}
|
|
||||||
|
|
||||||
println("versionCode for $abi = $versionCodeOverride")
|
println("versionCode for $abi = $versionCodeOverride")
|
||||||
|
|
||||||
|
@ -377,8 +315,11 @@ ext.gleanDocsDirectory = "$rootDir/docs"
|
||||||
apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"
|
apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
geckoNightlyImplementation Deps.mozilla_browser_engine_gecko_nightly
|
debugImplementation Deps.mozilla_browser_engine_gecko_nightly
|
||||||
geckoBetaImplementation Deps.mozilla_browser_engine_gecko_beta
|
|
||||||
|
nightlyImplementation Deps.mozilla_browser_engine_gecko_nightly
|
||||||
|
betaImplementation Deps.mozilla_browser_engine_gecko_beta
|
||||||
|
releaseImplementation Deps.mozilla_browser_engine_gecko_release
|
||||||
|
|
||||||
implementation Deps.kotlin_stdlib
|
implementation Deps.kotlin_stdlib
|
||||||
implementation Deps.kotlin_coroutines
|
implementation Deps.kotlin_coroutines
|
||||||
|
@ -604,17 +545,15 @@ task printVariants {
|
||||||
fileName: it.outputFileName,
|
fileName: it.outputFileName,
|
||||||
]},
|
]},
|
||||||
build_type: it.buildType.name,
|
build_type: it.buildType.name,
|
||||||
engine: it.productFlavors.find { it.dimension == 'engine' }.name,
|
|
||||||
name: it.name,
|
name: it.name,
|
||||||
]}
|
]}
|
||||||
// AndroidTest is a special case not included above
|
// AndroidTest is a special case not included above
|
||||||
variants.add([
|
variants.add([
|
||||||
apks: [[
|
apks: [[
|
||||||
abi: 'noarch',
|
abi: 'noarch',
|
||||||
fileName: 'app-geckoNightly-debug-androidTest.apk',
|
fileName: 'app-debug-androidTest.apk',
|
||||||
]],
|
]],
|
||||||
build_type: 'androidTest',
|
build_type: 'androidTest',
|
||||||
engine: 'geckoNightly',
|
|
||||||
name: 'androidTest',
|
name: 'androidTest',
|
||||||
])
|
])
|
||||||
println 'variants: ' + groovy.json.JsonOutput.toJson(variants)
|
println 'variants: ' + groovy.json.JsonOutput.toJson(variants)
|
||||||
|
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 22 KiB |
|
@ -1,29 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<shortcut
|
|
||||||
android:shortcutId="open_new_tab"
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/ic_static_shortcut_tab"
|
|
||||||
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_tab_2"
|
|
||||||
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_tab_2">
|
|
||||||
<intent
|
|
||||||
android:action="org.mozilla.fenix.OPEN_TAB"
|
|
||||||
android:targetPackage="org.mozilla.fenix.beta"
|
|
||||||
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:shortcutId="open_new_private_tab"
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/ic_static_shortcut_private_tab"
|
|
||||||
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_private_tab_2"
|
|
||||||
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_private_tab_2">
|
|
||||||
<intent
|
|
||||||
android:action="org.mozilla.fenix.OPEN_PRIVATE_TAB"
|
|
||||||
android:targetPackage="org.mozilla.fenix.beta"
|
|
||||||
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
|
|
||||||
</shortcut>
|
|
||||||
</shortcuts>
|
|
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 21 KiB |
|
@ -1,29 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
|
|
||||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<shortcut
|
|
||||||
android:shortcutId="open_new_tab"
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/ic_static_shortcut_tab"
|
|
||||||
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_tab_2"
|
|
||||||
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_tab_2">
|
|
||||||
<intent
|
|
||||||
android:action="org.mozilla.fenix.OPEN_TAB"
|
|
||||||
android:targetPackage="org.mozilla.fenix.nightly"
|
|
||||||
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:shortcutId="open_new_private_tab"
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/ic_static_shortcut_private_tab"
|
|
||||||
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_private_tab_2"
|
|
||||||
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_private_tab_2">
|
|
||||||
<intent
|
|
||||||
android:action="org.mozilla.fenix.OPEN_PRIVATE_TAB"
|
|
||||||
android:targetPackage="org.mozilla.fenix.nightly"
|
|
||||||
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
|
|
||||||
</shortcut>
|
|
||||||
</shortcuts>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="org.mozilla.fenix">
|
|
||||||
|
|
||||||
<application>
|
|
||||||
|
|
||||||
<service android:name=".customtabs.CustomTabsService">
|
|
||||||
<!-- Trusted Web Activities are currently only supported in nightly. -->
|
|
||||||
<intent-filter tools:node="removeAll" />
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.support.customtabs.action.CustomTabsService" />
|
|
||||||
<category android:name="androidx.browser.trusted.category.TrustedWebActivities" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
<resources>
|
|
||||||
<color name="ic_launcher_background">@color/nightly_launcher_background</color>
|
|
||||||
</resources>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
<resources>
|
|
||||||
<!-- Name of the application -->
|
|
||||||
<string name="app_name" translatable="false">Firefox Nightly</string>
|
|
||||||
</resources>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground_fennec_nightly"/>
|
|
||||||
</adaptive-icon>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground_fennec_nightly"/>
|
|
||||||
</adaptive-icon>
|
|
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 22 KiB |
|
@ -1,7 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
<resources>
|
|
||||||
<color name="ic_launcher_background">@color/debug_launcher_background</color>
|
|
||||||
</resources>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
<resources>
|
|
||||||
<!-- Name of the application -->
|
|
||||||
<string name="app_name" translatable="false">Firefox Nightly</string>
|
|
||||||
</resources>
|
|
|
@ -1,28 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
|
||||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<shortcut
|
|
||||||
android:shortcutId="open_new_tab"
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/ic_static_shortcut_tab"
|
|
||||||
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_tab_2"
|
|
||||||
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_tab_2">
|
|
||||||
<intent
|
|
||||||
android:action="org.mozilla.fenix.OPEN_TAB"
|
|
||||||
android:targetPackage="org.mozilla.fennec_aurora"
|
|
||||||
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
|
|
||||||
</shortcut>
|
|
||||||
<shortcut
|
|
||||||
android:shortcutId="open_new_private_tab"
|
|
||||||
android:enabled="true"
|
|
||||||
android:icon="@drawable/ic_static_shortcut_private_tab"
|
|
||||||
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_private_tab_2"
|
|
||||||
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_private_tab_2">
|
|
||||||
<intent
|
|
||||||
android:action="org.mozilla.fenix.OPEN_PRIVATE_TAB"
|
|
||||||
android:targetPackage="org.mozilla.fennec_aurora"
|
|
||||||
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
|
|
||||||
</shortcut>
|
|
||||||
</shortcuts>
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper
|
||||||
|
import mozilla.components.browser.engine.gecko.glean.GeckoAdapter
|
||||||
|
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy
|
||||||
|
import mozilla.components.concept.storage.LoginsStorage
|
||||||
|
import mozilla.components.lib.crash.handler.CrashHandlerService
|
||||||
|
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
|
||||||
|
import org.mozilla.fenix.Config
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.geckoview.GeckoRuntime
|
||||||
|
import org.mozilla.geckoview.GeckoRuntimeSettings
|
||||||
|
|
||||||
|
object GeckoProvider {
|
||||||
|
var testConfig: Bundle? = null
|
||||||
|
private var runtime: GeckoRuntime? = null
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun getOrCreateRuntime(
|
||||||
|
context: Context,
|
||||||
|
storage: Lazy<LoginsStorage>,
|
||||||
|
trackingProtectionPolicy: TrackingProtectionPolicy
|
||||||
|
): GeckoRuntime {
|
||||||
|
if (runtime == null) {
|
||||||
|
runtime = createRuntime(context, storage, trackingProtectionPolicy)
|
||||||
|
}
|
||||||
|
|
||||||
|
return runtime!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRuntime(
|
||||||
|
context: Context,
|
||||||
|
storage: Lazy<LoginsStorage>,
|
||||||
|
policy: TrackingProtectionPolicy
|
||||||
|
): GeckoRuntime {
|
||||||
|
val builder = GeckoRuntimeSettings.Builder()
|
||||||
|
|
||||||
|
testConfig?.let {
|
||||||
|
builder.extras(it)
|
||||||
|
.remoteDebuggingEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use meeee.
|
||||||
|
policy.hashCode()
|
||||||
|
|
||||||
|
val runtimeSettings = builder
|
||||||
|
.crashHandler(CrashHandlerService::class.java)
|
||||||
|
.telemetryDelegate(GeckoAdapter())
|
||||||
|
// TODO: Fix me!
|
||||||
|
// .contentBlocking(policy.toContentBlockingSetting())
|
||||||
|
.aboutConfigEnabled(Config.channel.isBeta)
|
||||||
|
.debugLogging(Config.channel.isDebug)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val settings = context.components.settings
|
||||||
|
if (!settings.shouldUseAutoSize) {
|
||||||
|
runtimeSettings.automaticFontSizeAdjustment = false
|
||||||
|
val fontSize = settings.fontSizeFactor
|
||||||
|
runtimeSettings.fontSizeFactor = fontSize
|
||||||
|
}
|
||||||
|
|
||||||
|
val geckoRuntime = GeckoRuntime.create(context, runtimeSettings)
|
||||||
|
val loginStorageDelegate = GeckoLoginStorageDelegate(storage)
|
||||||
|
geckoRuntime.loginStorageDelegate = GeckoLoginDelegateWrapper(loginStorageDelegate)
|
||||||
|
|
||||||
|
return geckoRuntime
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,14 +5,14 @@
|
||||||
package org.mozilla.fenix
|
package org.mozilla.fenix
|
||||||
|
|
||||||
enum class ReleaseChannel {
|
enum class ReleaseChannel {
|
||||||
FenixDebug,
|
Debug,
|
||||||
FenixProduction,
|
Nightly,
|
||||||
FennecProduction,
|
Beta,
|
||||||
FennecBeta;
|
Release;
|
||||||
|
|
||||||
val isReleased: Boolean
|
val isReleased: Boolean
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
FenixDebug -> false
|
Debug -> false
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,51 +27,39 @@ enum class ReleaseChannel {
|
||||||
get() = !this.isReleased
|
get() = !this.isReleased
|
||||||
|
|
||||||
val isReleaseOrBeta: Boolean
|
val isReleaseOrBeta: Boolean
|
||||||
get() = when (this) {
|
get() = this == Release || this == Beta
|
||||||
FennecProduction -> true
|
|
||||||
FennecBeta -> true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
|
|
||||||
val isRelease: Boolean
|
val isRelease: Boolean
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
FennecProduction -> true
|
Release -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
val isBeta: Boolean
|
val isBeta: Boolean
|
||||||
get() = when (this) {
|
get() = this == Beta
|
||||||
FennecBeta -> true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
|
|
||||||
val isNightlyOrDebug: Boolean
|
val isNightlyOrDebug: Boolean
|
||||||
get() = when (this) {
|
get() = this == Debug || this == Nightly
|
||||||
FenixDebug -> true
|
|
||||||
FenixProduction -> true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this a build for a release channel that we used to ship Fennec on?
|
||||||
|
*/
|
||||||
val isFennec: Boolean
|
val isFennec: Boolean
|
||||||
get() = this in fennecChannels
|
get() = this in fennecChannels
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this build for a "pure" Fenix channel that we never shipped Fennec on?
|
||||||
|
*/
|
||||||
val isFenix: Boolean
|
val isFenix: Boolean
|
||||||
get() = !isFennec
|
get() = !isFennec
|
||||||
}
|
}
|
||||||
|
|
||||||
object Config {
|
object Config {
|
||||||
val channel = when (BuildConfig.BUILD_TYPE) {
|
val channel = when (BuildConfig.BUILD_TYPE) {
|
||||||
"fenixProduction" -> ReleaseChannel.FenixProduction
|
"debug" -> ReleaseChannel.Debug
|
||||||
"debug" -> ReleaseChannel.FenixDebug
|
"nightly" -> ReleaseChannel.Nightly
|
||||||
"fennecProduction" -> ReleaseChannel.FennecProduction
|
"beta" -> ReleaseChannel.Beta
|
||||||
"fennecBeta" -> ReleaseChannel.FennecBeta
|
"release" -> ReleaseChannel.Release
|
||||||
|
|
||||||
// Builds for local performance analysis, recording benchmarks, automation, etc.
|
|
||||||
// This should be treated like a released channel because we want to test
|
|
||||||
// what users experience and there are performance-impacting changes in debug
|
|
||||||
// release channels (e.g. logging) that are never intended to be shipped.
|
|
||||||
"forPerformanceTest" -> ReleaseChannel.FenixProduction
|
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
throw IllegalStateException("Unknown build type: ${BuildConfig.BUILD_TYPE}")
|
throw IllegalStateException("Unknown build type: ${BuildConfig.BUILD_TYPE}")
|
||||||
}
|
}
|
||||||
|
@ -79,6 +67,6 @@ object Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
private val fennecChannels: List<ReleaseChannel> = listOf(
|
private val fennecChannels: List<ReleaseChannel> = listOf(
|
||||||
ReleaseChannel.FennecBeta,
|
ReleaseChannel.Beta,
|
||||||
ReleaseChannel.FennecProduction
|
ReleaseChannel.Release
|
||||||
)
|
)
|
||||||
|
|
|
@ -102,9 +102,9 @@ fun isSentryEnabled() = !BuildConfig.SENTRY_TOKEN.isNullOrEmpty()
|
||||||
private fun getSentryProjectUrl(): String? {
|
private fun getSentryProjectUrl(): String? {
|
||||||
val baseUrl = "https://sentry.prod.mozaws.net/operations"
|
val baseUrl = "https://sentry.prod.mozaws.net/operations"
|
||||||
return when (Config.channel) {
|
return when (Config.channel) {
|
||||||
ReleaseChannel.FenixProduction -> "$baseUrl/fenix"
|
ReleaseChannel.Nightly -> "$baseUrl/fenix"
|
||||||
ReleaseChannel.FennecProduction -> "$baseUrl/fenix-fennec"
|
ReleaseChannel.Release -> "$baseUrl/fenix-fennec"
|
||||||
ReleaseChannel.FennecBeta -> "$baseUrl/fenix-fennec-beta"
|
ReleaseChannel.Beta -> "$baseUrl/fenix-fennec-beta"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |