1
0
Fork 0

Copione merged onto master

master
blallo 2020-08-07 00:00:23 +02:00
commit 90e4a38f45
178 changed files with 732 additions and 813 deletions

View File

@ -81,52 +81,45 @@ Pre-requisites:
2. **Import** the project into Android Studio **or** build on the command line:
```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
variable pointing to the right path.
3. Make sure to select the correct build variant in Android Studio. See the next section.
### Guide to Build Variants
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:
### Build Variants
For general development, we recommend the **debug** build variant. Here's an explanation of each variant:
- **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
- **debug**: the default for developers, similar to most other Android apps. It is debuggable, uses a Nightly GeckoView with debug symbols, adds tools like LeakCanary for troublingshooting, and does not strip unused code.
- **nightly**: what we ship to the Firefox Nightly channel, using GeckoView Nightly.
- **beta**: what we ship to the Firefox Beta channel, using GeckoView Beta. It is more stable than nightly.
- **release**: what we ship as Firefox for Android, using GeckoView Release. It is the most stable.
<br />
<br />
- **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
- **fenixBeta** is a release build with beta signing which uses the org.mozilla.fenix.beta app id for beta releases to Google Play
- **fenixProduction** is a release build with release signing which uses the org.mozilla.fenix 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.
nightly, beta, and release are unsigned and `debuggable=false` by default. If
you want these variants to be:
- automatically signed, see [Automatically signing release builds](#automatically-sign-release-builds)
- `debuggable=true`, see [Building debuggable release variants](#building-debuggable-release-variants)
#### Performance Build Variants
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):
- Recommendation: use a `forPerformanceTest` variant 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.
- 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: 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:
- 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).
- 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:
- 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.
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
To reduce review turn-around time, we'd like all pushes to run tests locally. We'd
@ -181,6 +174,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.
### 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
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.

View File

@ -6,7 +6,6 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'jacoco'
apply from: "$project.rootDir/automation/gradle/versionCode.gradle"
apply plugin: 'androidx.navigation.safeargs.kotlin'
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
@ -48,6 +47,10 @@ android {
if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey")) {
signingConfig signingConfigs.debug
}
if (gradle.hasProperty("localProperties.debuggable")) {
debuggable true
}
}
buildTypes {
@ -58,25 +61,31 @@ android {
resValue "bool", "IS_DEBUG", "true"
pseudoLocalesEnabled true
}
forPerformanceTest releaseTemplate >> { // the ">>" concatenates the raptor-specific options with the template
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 >> {
nightly releaseTemplate >> {
applicationIdSuffix ".fenix"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
def deepLinkSchemeValue = "fenix-nightly"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$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&regexp=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"
applicationIdSuffix ".firefox"
def deepLinkSchemeValue = "fenix"
@ -93,49 +102,6 @@ android {
"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&regexp=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 {
@ -162,30 +128,20 @@ android {
androidTest {
resources.srcDirs += ['src/androidTest/resources']
}
fennecBeta {
java.srcDirs = ['src/migration/java']
debug {
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"
}
fennecProduction {
java.srcDirs = ['src/migration/java']
release {
java.srcDirs = ['src/migration/java', 'src/geckoRelease/java']
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 {
@ -216,8 +172,6 @@ android {
}
}
def baseVersionCode = generatedVersionCode
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
// 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 ->
def abi = output.getFilter(OutputFile.ABI)
def versionCodeOverride
if (variant.name.contains("Fennec")) {
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")
}
// We use the same version code generator, that we inherited from Fennec, across all channels - even on
// channels that never shipped a Fennec build.
def versionCodeOverride = Config.generateFennecVersionCode(abi)
println("versionCode for $abi = $versionCodeOverride")
@ -377,8 +315,11 @@ ext.gleanDocsDirectory = "$rootDir/docs"
apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"
dependencies {
geckoNightlyImplementation Deps.mozilla_browser_engine_gecko_nightly
geckoBetaImplementation Deps.mozilla_browser_engine_gecko_beta
debugImplementation Deps.mozilla_browser_engine_gecko_nightly
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_coroutines
@ -604,17 +545,15 @@ task printVariants {
fileName: it.outputFileName,
]},
build_type: it.buildType.name,
engine: it.productFlavors.find { it.dimension == 'engine' }.name,
name: it.name,
]}
// AndroidTest is a special case not included above
variants.add([
apks: [[
abi: 'noarch',
fileName: 'app-geckoNightly-debug-androidTest.apk',
fileName: 'app-debug-androidTest.apk',
]],
build_type: 'androidTest',
engine: 'geckoNightly',
name: 'androidTest',
])
println 'variants: ' + groovy.json.JsonOutput.toJson(variants)

View File

@ -10,7 +10,6 @@ import okhttp3.mockwebserver.MockWebServer
import org.junit.Rule
import org.junit.Before
import org.junit.After
import org.junit.Ignore
import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
@ -44,7 +43,6 @@ class SettingsAddonsTest {
}
// Walks through settings add-ons menu to ensure all items are present
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/13218")
@Test
fun settingsAddonsItemsTest() {
homeScreen {
@ -76,7 +74,6 @@ class SettingsAddonsTest {
}
}
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/13220")
// Opens the addons settings menu, installs an addon, then uninstalls
@Test
fun verifyAddonsCanBeUninstalled() {

View File

@ -2,8 +2,9 @@ package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.hamcrest.CoreMatchers.allOf
import org.mozilla.fenix.R
@ -35,11 +36,11 @@ class SettingsSubMenuAddonsManagerAddonDetailedMenuRobot {
}
private fun assertAddonMenuItems() {
enableSwitchButton().check(matches(isCompletelyDisplayed()))
settingsButton().check(matches(isCompletelyDisplayed()))
detailsButton().check(matches(isCompletelyDisplayed()))
permissionsButton().check(matches(isCompletelyDisplayed()))
removeAddonButton().check(matches(isCompletelyDisplayed()))
enableSwitchButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
settingsButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
detailsButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
permissionsButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
removeAddonButton().check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
}
}

View File

@ -31,6 +31,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
@ -350,10 +351,9 @@ class ThreeDotMenuMainRobot {
fun openAddonsManagerMenu(interact: SettingsSubMenuAddonsManagerRobot.() -> Unit): SettingsSubMenuAddonsManagerRobot.Transition {
clickAddonsManagerButton()
mDevice.waitNotNull(
Until.findObject(By.text("Recommended")),
waitingTime
)
mDevice.findObject(
UiSelector().text("Recommended")
).waitForExists(waitingTime)
SettingsSubMenuAddonsManagerRobot().interact()
return SettingsSubMenuAddonsManagerRobot.Transition()

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -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>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
}
}

View File

@ -17,6 +17,7 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromGlobal(0),
FromHome(R.id.homeFragment),
FromSearch(R.id.searchFragment),
FromSearchDialog(R.id.searchDialogFragment),
FromSettings(R.id.settingsFragment),
FromSyncedTabs(R.id.syncedTabsFragment),
FromBookmarks(R.id.bookmarkFragment),

View File

@ -5,14 +5,14 @@
package org.mozilla.fenix
enum class ReleaseChannel {
FenixDebug,
FenixProduction,
FennecProduction,
FennecBeta;
Debug,
Nightly,
Beta,
Release;
val isReleased: Boolean
get() = when (this) {
FenixDebug -> false
Debug -> false
else -> true
}
@ -27,51 +27,39 @@ enum class ReleaseChannel {
get() = !this.isReleased
val isReleaseOrBeta: Boolean
get() = when (this) {
FennecProduction -> true
FennecBeta -> true
else -> false
}
get() = this == Release || this == Beta
val isRelease: Boolean
get() = when (this) {
FennecProduction -> true
Release -> true
else -> false
}
val isBeta: Boolean
get() = when (this) {
FennecBeta -> true
else -> false
}
get() = this == Beta
val isNightlyOrDebug: Boolean
get() = when (this) {
FenixDebug -> true
FenixProduction -> true
else -> false
}
get() = this == Debug || this == Nightly
/**
* Is this a build for a release channel that we used to ship Fennec on?
*/
val isFennec: Boolean
get() = this in fennecChannels
/**
* Is this build for a "pure" Fenix channel that we never shipped Fennec on?
*/
val isFenix: Boolean
get() = !isFennec
}
object Config {
val channel = when (BuildConfig.BUILD_TYPE) {
"fenixProduction" -> ReleaseChannel.FenixProduction
"debug" -> ReleaseChannel.FenixDebug
"fennecProduction" -> ReleaseChannel.FennecProduction
"fennecBeta" -> ReleaseChannel.FennecBeta
// 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
"debug" -> ReleaseChannel.Debug
"nightly" -> ReleaseChannel.Nightly
"beta" -> ReleaseChannel.Beta
"release" -> ReleaseChannel.Release
else -> {
throw IllegalStateException("Unknown build type: ${BuildConfig.BUILD_TYPE}")
}
@ -79,6 +67,6 @@ object Config {
}
private val fennecChannels: List<ReleaseChannel> = listOf(
ReleaseChannel.FennecBeta,
ReleaseChannel.FennecProduction
ReleaseChannel.Beta,
ReleaseChannel.Release
)

View File

@ -86,6 +86,7 @@ import org.mozilla.fenix.library.history.HistoryFragmentDirections
import org.mozilla.fenix.perf.Performance
import org.mozilla.fenix.perf.StartupTimeline
import org.mozilla.fenix.search.SearchFragmentDirections
import org.mozilla.fenix.searchdialog.SearchDialogFragmentDirections
import org.mozilla.fenix.session.NotificationSessionObserver
import org.mozilla.fenix.settings.SettingsFragmentDirections
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
@ -553,9 +554,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
BrowserDirection.FromGlobal ->
NavGraphDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromHome ->
HomeFragmentDirections.actionHomeFragmentToBrowserFragment(customTabSessionId, true)
HomeFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSearch ->
SearchFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSearchDialog ->
SearchDialogFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSettings ->
SettingsFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSyncedTabs ->

View File

@ -10,6 +10,7 @@ import android.os.Bundle
import android.os.StrictMode
import androidx.annotation.VisibleForTesting
import mozilla.components.feature.intent.processing.IntentProcessor
import org.mozilla.fenix.HomeActivity.Companion.PRIVATE_BROWSING_MODE
import org.mozilla.fenix.components.IntentProcessorType
import org.mozilla.fenix.components.getType
import org.mozilla.fenix.components.metrics.Event
@ -43,7 +44,15 @@ class IntentReceiverActivity : Activity() {
fun processIntent(intent: Intent) {
// Call process for side effects, short on the first that returns true
val processor = getIntentProcessors().firstOrNull { it.process(intent) }
val private = settings().openLinksInAPrivateTab
intent.putExtra(PRIVATE_BROWSING_MODE, private)
if (private) {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.PRIVATE))
} else {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.NORMAL))
}
val processor = getIntentProcessors(private).firstOrNull { it.process(intent) }
val intentProcessorType = components.intentProcessors.getType(processor)
launch(intent, intentProcessorType)
@ -65,17 +74,14 @@ class IntentReceiverActivity : Activity() {
finish() // must finish() after starting the other activity
}
private fun getIntentProcessors(): List<IntentProcessor> {
val modeDependentProcessors = if (settings().openLinksInAPrivateTab) {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.PRIVATE))
intent.putExtra(HomeActivity.PRIVATE_BROWSING_MODE, true)
private fun getIntentProcessors(private: Boolean): List<IntentProcessor> {
val modeDependentProcessors = if (private) {
listOf(
components.intentProcessors.privateCustomTabIntentProcessor,
components.intentProcessors.privateIntentProcessor
)
} else {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.NORMAL))
intent.putExtra(HomeActivity.PRIVATE_BROWSING_MODE, false)
listOf(
components.intentProcessors.customTabIntentProcessor,
components.intentProcessors.intentProcessor

View File

@ -198,8 +198,7 @@ class InstalledAddonDetailsFragment : Fragment() {
components.useCases.tabsUseCases.addTab(settingUrl)
}
InstalledAddonDetailsFragmentDirections
.actionGlobalBrowser(null, false)
InstalledAddonDetailsFragmentDirections.actionGlobalBrowser(null)
} else {
InstalledAddonDetailsFragmentDirections
.actionInstalledAddonFragmentToAddonInternalSettingsFragment(addon)

View File

@ -204,7 +204,6 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
engineView = WeakReference(engineView),
swipeRefresh = WeakReference(swipeRefresh),
viewLifecycleScope = WeakReference(viewLifecycleOwner.lifecycleScope),
arguments = requireArguments(),
firstContentfulHappened = ::didFirstContentfulHappen
).apply {
beginAnimateInIfNecessary()
@ -586,8 +585,15 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
}
.ifChanged { it.content.firstContentfulPaint }
.collect {
engineView?.asView()?.isVisible =
val showEngineView =
it.content.firstContentfulPaint || it.content.progress == 100
if (showEngineView) {
engineView?.asView()?.isVisible = true
swipeRefresh.alpha = 1f
} else {
engineView?.asView()?.isVisible = false
}
}
}
}

View File

@ -4,19 +4,14 @@
package org.mozilla.fenix.browser
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.View
import android.view.animation.DecelerateInterpolator
import androidx.core.animation.doOnEnd
import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.navigation.NavOptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import mozilla.components.concept.engine.EngineView
@ -34,7 +29,6 @@ class BrowserAnimator(
private val engineView: WeakReference<EngineView>,
private val swipeRefresh: WeakReference<View>,
private val viewLifecycleScope: WeakReference<LifecycleCoroutineScope>,
private val arguments: Bundle,
private val firstContentfulHappened: () -> Boolean
) {
@ -44,46 +38,19 @@ class BrowserAnimator(
private val unwrappedSwipeRefresh: View?
get() = swipeRefresh.get()
private val browserZoomInValueAnimator = ValueAnimator.ofFloat(0f, END_ANIMATOR_VALUE).apply {
addUpdateListener {
unwrappedSwipeRefresh?.scaleX =
STARTING_XY_SCALE + XY_SCALE_MULTIPLIER * it.animatedFraction
unwrappedSwipeRefresh?.scaleY =
STARTING_XY_SCALE + XY_SCALE_MULTIPLIER * it.animatedFraction
unwrappedSwipeRefresh?.alpha = it.animatedFraction
}
doOnEnd {
if (firstContentfulHappened()) {
unwrappedEngineView?.asView()?.visibility = View.VISIBLE
}
unwrappedSwipeRefresh?.background = null
arguments.putBoolean(SHOULD_ANIMATE_FLAG, false)
}
interpolator = DecelerateInterpolator()
duration = ANIMATION_DURATION
}
/**
* Triggers the *zoom in* browser animation to run if necessary (based on the SHOULD_ANIMATE_FLAG).
* Also removes the flag from the bundle so it is not played on future entries into the fragment.
*/
fun beginAnimateInIfNecessary() {
val shouldAnimate = arguments.getBoolean(SHOULD_ANIMATE_FLAG, false)
if (shouldAnimate) {
viewLifecycleScope.get()?.launch(Dispatchers.Main) {
delay(ANIMATION_DELAY)
captureEngineViewAndDrawStatically {
unwrappedSwipeRefresh?.alpha = 0f
browserZoomInValueAnimator.start()
if (unwrappedSwipeRefresh?.context?.settings()?.waitToShowPageUntilFirstPaint == true) {
if (firstContentfulHappened()) {
viewLifecycleScope.get()?.launch {
delay(100)
unwrappedEngineView?.asView()?.visibility = View.VISIBLE
unwrappedSwipeRefresh?.background = null
unwrappedSwipeRefresh?.alpha = 1f
}
}
} else {
unwrappedSwipeRefresh?.alpha = 1f
if (firstContentfulHappened()) {
unwrappedEngineView?.asView()?.visibility = View.VISIBLE
}
unwrappedEngineView?.asView()?.visibility = View.VISIBLE
unwrappedSwipeRefresh?.background = null
}
}
@ -124,13 +91,6 @@ class BrowserAnimator(
}
companion object {
private const val SHOULD_ANIMATE_FLAG = "shouldAnimate"
private const val ANIMATION_DELAY = 50L
private const val ANIMATION_DURATION = 115L
private const val END_ANIMATOR_VALUE = 500f
private const val XY_SCALE_MULTIPLIER = .05f
private const val STARTING_XY_SCALE = .95f
fun getToolbarNavOptions(context: Context): NavOptions {
val navOptions = NavOptions.Builder()

View File

@ -102,9 +102,9 @@ fun isSentryEnabled() = !BuildConfig.SENTRY_TOKEN.isNullOrEmpty()
private fun getSentryProjectUrl(): String? {
val baseUrl = "https://sentry.prod.mozaws.net/operations"
return when (Config.channel) {
ReleaseChannel.FenixProduction -> "$baseUrl/fenix"
ReleaseChannel.FennecProduction -> "$baseUrl/fenix-fennec"
ReleaseChannel.FennecBeta -> "$baseUrl/fenix-fennec-beta"
ReleaseChannel.Nightly -> "$baseUrl/fenix"
ReleaseChannel.Release -> "$baseUrl/fenix-fennec"
ReleaseChannel.Beta -> "$baseUrl/fenix-fennec-beta"
else -> null
}
}

View File

@ -81,6 +81,9 @@ class DefaultBrowserToolbarController(
private val onCloseTab: (Session) -> Unit
) : BrowserToolbarController {
private val useNewSearchExperience
get() = activity.settings().useNewSearchExperience
private val currentSession
get() = customTabSession ?: activity.components.core.sessionManager.selectedSession
@ -91,11 +94,25 @@ class DefaultBrowserToolbarController(
internal var ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
override fun handleToolbarPaste(text: String) {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = currentSession?.id,
pastedText = text
)
navController.nav(R.id.browserFragment, directions, getToolbarNavOptions(activity))
if (useNewSearchExperience) {
navController.nav(
R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = currentSession?.id,
pastedText = text
), getToolbarNavOptions(activity)
)
} else {
browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = currentSession?.id,
pastedText = text
),
getToolbarNavOptions(activity)
)
}
}
}
override fun handleToolbarPasteAndGo(text: String) {
@ -117,11 +134,23 @@ class DefaultBrowserToolbarController(
Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)
)
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
currentSession?.id
if (useNewSearchExperience) {
navController.nav(
R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog(
currentSession?.id
), getToolbarNavOptions(activity)
)
navController.nav(R.id.browserFragment, directions, getToolbarNavOptions(activity))
} else {
browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
currentSession?.id
),
getToolbarNavOptions(activity)
)
}
}
}
override fun handleTabCounterClick() {
@ -146,7 +175,11 @@ class DefaultBrowserToolbarController(
if (sessionManager.sessionsOfType(it.private).count() == 1) {
// The tab tray always returns to normal mode so do that here too
activity.browsingModeManager.mode = BrowsingMode.Normal
navController.navigate(BrowserFragmentDirections.actionGlobalHome(sessionToDelete = it.id))
navController.navigate(
BrowserFragmentDirections.actionGlobalHome(
sessionToDelete = it.id
)
)
} else {
onCloseTab.invoke(it)
activity.components.useCases.tabsUseCases.removeTab.invoke(it)
@ -195,7 +228,7 @@ class DefaultBrowserToolbarController(
val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
navController.nav(R.id.browserFragment, directions)
}
ToolbarMenu.Item.SyncedTabs -> {
ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment()
@ -256,7 +289,7 @@ class DefaultBrowserToolbarController(
activity.components.analytics.metrics.track(Event.FindInPageOpened)
}
ToolbarMenu.Item.AddonsManager -> {
ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalAddonsManagementFragment()
@ -332,13 +365,13 @@ class DefaultBrowserToolbarController(
bookmarkTapped(it)
}
}
ToolbarMenu.Item.Bookmarks -> {
ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
)
}
ToolbarMenu.Item.History -> {
ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalHistoryFragment()

View File

@ -478,9 +478,7 @@ class HomeFragment : Fragment() {
engineSessionState = state
)
findNavController().navigate(
HomeFragmentDirections.actionHomeFragmentToBrowserFragment(
null
)
HomeFragmentDirections.actionGlobalBrowser(null)
)
},
operation = { },
@ -674,7 +672,9 @@ class HomeFragment : Fragment() {
private fun navigateToSearch() {
val directions = if (requireContext().settings().useNewSearchExperience) {
HomeFragmentDirections.actionGlobalSearchDialog()
HomeFragmentDirections.actionGlobalSearchDialog(
sessionId = null
)
} else {
HomeFragmentDirections.actionGlobalSearch(
sessionId = null

View File

@ -91,7 +91,7 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
override fun getItemCount(): Int = tree.size
override fun onBindViewHolder(holder: BookmarkNodeViewHolder, position: Int) {
holder.bind(tree[position])
holder.bind(tree[position], mode)
}
}

View File

@ -139,6 +139,7 @@ class BookmarkView(
}
fun update(state: BookmarkFragmentState) {
val oldMode = mode
tree = state.tree
if (state.mode != mode) {
mode = state.mode
@ -148,6 +149,10 @@ class BookmarkView(
}
bookmarkAdapter.updateData(state.tree, mode)
if (state.mode != oldMode) {
bookmarkAdapter.notifyDataSetChanged()
}
when (mode) {
is BookmarkFragmentState.Mode.Normal -> {
setUiForNormalMode(state.tree)

View File

@ -13,6 +13,7 @@ import org.mozilla.fenix.ext.hideAndDisable
import org.mozilla.fenix.ext.showAndEnable
import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.SelectionHolder
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
import org.mozilla.fenix.library.bookmarks.inRoots
@ -27,7 +28,10 @@ class BookmarkFolderViewHolder(
override var item: BookmarkNode? = null
override fun bind(item: BookmarkNode) {
override fun bind(
item: BookmarkNode,
mode: BookmarkFragmentState.Mode
) {
this.item = item
containerView.displayAs(LibrarySiteItemView.ItemType.FOLDER)
@ -36,10 +40,10 @@ class BookmarkFolderViewHolder(
if (!item.inRoots()) {
setupMenu(item)
if (selectionHolder.selectedItems.isEmpty()) {
containerView.overflowView.showAndEnable()
} else {
if (mode is BookmarkFragmentState.Mode.Selecting) {
containerView.overflowView.hideAndDisable()
} else {
containerView.overflowView.showAndEnable()
}
} else {
containerView.overflowView.visibility = View.GONE

View File

@ -9,6 +9,7 @@ import org.mozilla.fenix.ext.hideAndDisable
import org.mozilla.fenix.ext.showAndEnable
import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.SelectionHolder
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
/**
@ -22,15 +23,18 @@ class BookmarkItemViewHolder(
override var item: BookmarkNode? = null
override fun bind(item: BookmarkNode) {
override fun bind(
item: BookmarkNode,
mode: BookmarkFragmentState.Mode
) {
this.item = item
containerView.displayAs(LibrarySiteItemView.ItemType.SITE)
if (selectionHolder.selectedItems.isEmpty()) {
containerView.overflowView.showAndEnable()
} else {
if (mode is BookmarkFragmentState.Mode.Selecting) {
containerView.overflowView.hideAndDisable()
} else {
containerView.overflowView.showAndEnable()
}
setupMenu(item)
containerView.titleView.text = if (item.title.isNullOrBlank()) item.url else item.title

View File

@ -9,6 +9,7 @@ import kotlinx.android.extensions.LayoutContainer
import mozilla.components.concept.storage.BookmarkNode
import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.SelectionHolder
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
import org.mozilla.fenix.library.bookmarks.BookmarkItemMenu
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
@ -22,7 +23,10 @@ abstract class BookmarkNodeViewHolder(
abstract var item: BookmarkNode?
abstract fun bind(item: BookmarkNode)
abstract fun bind(
item: BookmarkNode,
mode: BookmarkFragmentState.Mode
)
protected fun setSelectionListeners(item: BookmarkNode, selectionHolder: SelectionHolder<BookmarkNode>) {
containerView.setSelectionInteractor(item, selectionHolder, interactor)

View File

@ -6,6 +6,7 @@ package org.mozilla.fenix.library.bookmarks.viewholders
import mozilla.components.concept.storage.BookmarkNode
import org.mozilla.fenix.library.LibrarySiteItemView
import org.mozilla.fenix.library.bookmarks.BookmarkFragmentState
import org.mozilla.fenix.library.bookmarks.BookmarkViewInteractor
/**
@ -18,7 +19,10 @@ class BookmarkSeparatorViewHolder(
override var item: BookmarkNode? = null
override fun bind(item: BookmarkNode) {
override fun bind(
item: BookmarkNode,
mode: BookmarkFragmentState.Mode
) {
this.item = item
containerView.displayAs(LibrarySiteItemView.ItemType.SEPARATOR)
setupMenu(item)

View File

@ -0,0 +1,180 @@
/* 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/. */
package org.mozilla.fenix.searchdialog
import android.content.Intent
import androidx.navigation.NavController
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.support.ktx.kotlin.isUrl
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
import org.mozilla.fenix.crashes.CrashListActivity
import org.mozilla.fenix.ext.navigateSafe
import org.mozilla.fenix.search.SearchController
import org.mozilla.fenix.search.SearchFragmentAction
import org.mozilla.fenix.search.SearchFragmentStore
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings
@Suppress("TooManyFunctions", "LongParameterList")
class SearchDialogController(
private val activity: HomeActivity,
private val sessionManager: SessionManager,
private val store: SearchFragmentStore,
private val navController: NavController,
private val settings: Settings,
private val metrics: MetricController,
private val clearToolbarFocus: () -> Unit
) : SearchController {
override fun handleUrlCommitted(url: String) {
when (url) {
"about:crashes" -> {
// The list of past crashes can be accessed via "settings > about", but desktop and
// fennec users may be used to navigating to "about:crashes". So we intercept this here
// and open the crash list activity instead.
activity.startActivity(Intent(activity, CrashListActivity::class.java))
}
"moz://a" -> openSearchOrUrl(SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.MANIFESTO))
else -> if (url.isNotBlank()) {
openSearchOrUrl(url)
}
}
}
private fun openSearchOrUrl(url: String) {
activity.openToBrowserAndLoad(
searchTermOrURL = url,
newTab = store.state.tabId == null,
from = BrowserDirection.FromSearchDialog,
engine = store.state.searchEngineSource.searchEngine
)
val event = if (url.isUrl()) {
Event.EnteredUrl(false)
} else {
settings.incrementActiveSearchCount()
val searchAccessPoint = when (store.state.searchAccessPoint) {
Event.PerformedSearch.SearchAccessPoint.NONE -> Event.PerformedSearch.SearchAccessPoint.ACTION
else -> store.state.searchAccessPoint
}
searchAccessPoint?.let { sap ->
MetricsUtils.createSearchEvent(
store.state.searchEngineSource.searchEngine,
activity,
sap
)
}
}
event?.let { metrics.track(it) }
}
override fun handleEditingCancelled() {
clearToolbarFocus()
}
override fun handleTextChanged(text: String) {
// Display the search shortcuts on each entry of the search fragment (see #5308)
val textMatchesCurrentUrl = store.state.url == text
val textMatchesCurrentSearch = store.state.searchTerms == text
store.dispatch(SearchFragmentAction.UpdateQuery(text))
store.dispatch(
SearchFragmentAction.ShowSearchShortcutEnginePicker(
(textMatchesCurrentUrl || textMatchesCurrentSearch || text.isEmpty()) &&
settings.shouldShowSearchShortcuts
)
)
store.dispatch(
SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt(
text.isNotEmpty() &&
activity.browsingModeManager.mode.isPrivate &&
!settings.shouldShowSearchSuggestionsInPrivate &&
!settings.showSearchSuggestionsInPrivateOnboardingFinished
)
)
}
override fun handleUrlTapped(url: String) {
clearToolbarFocus()
activity.openToBrowserAndLoad(
searchTermOrURL = url,
newTab = store.state.tabId == null,
from = BrowserDirection.FromSearchDialog
)
metrics.track(Event.EnteredUrl(false))
}
override fun handleSearchTermsTapped(searchTerms: String) {
settings.incrementActiveSearchCount()
clearToolbarFocus()
activity.openToBrowserAndLoad(
searchTermOrURL = searchTerms,
newTab = store.state.tabId == null,
from = BrowserDirection.FromSearchDialog,
engine = store.state.searchEngineSource.searchEngine,
forceSearch = true
)
val searchAccessPoint = when (store.state.searchAccessPoint) {
Event.PerformedSearch.SearchAccessPoint.NONE -> Event.PerformedSearch.SearchAccessPoint.SUGGESTION
else -> store.state.searchAccessPoint
}
val event = searchAccessPoint?.let { sap ->
MetricsUtils.createSearchEvent(
store.state.searchEngineSource.searchEngine,
activity,
sap
)
}
event?.let { metrics.track(it) }
}
override fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) {
store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine))
val isCustom =
CustomSearchEngineStore.isCustomSearchEngine(activity, searchEngine.identifier)
metrics.track(Event.SearchShortcutSelected(searchEngine, isCustom))
}
override fun handleSearchShortcutsButtonClicked() {
val isOpen = store.state.showSearchShortcuts
store.dispatch(SearchFragmentAction.ShowSearchShortcutEnginePicker(!isOpen))
}
override fun handleClickSearchEngineSettings() {
val directions = SearchDialogFragmentDirections.actionGlobalSearchEngineFragment()
navController.navigateSafe(R.id.searchDialogFragment, directions)
}
override fun handleExistingSessionSelected(session: Session) {
clearToolbarFocus()
sessionManager.select(session)
activity.openToBrowser(
from = BrowserDirection.FromSearchDialog
)
}
override fun handleExistingSessionSelected(tabId: String) {
val session = sessionManager.findSessionById(tabId)
if (session != null) {
handleExistingSessionSelected(session)
}
}
}

View File

@ -9,88 +9,43 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_search.view.*
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.Session
import kotlinx.android.synthetic.main.fragment_search_dialog.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.state.selector.findTab
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.ktx.android.view.hideKeyboard
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.logDebug
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.search.SearchEngineSource
import org.mozilla.fenix.search.SearchFragmentState
import org.mozilla.fenix.search.awesomebar.AwesomeBarInteractor
import org.mozilla.fenix.search.SearchFragmentStore
import org.mozilla.fenix.search.SearchInteractor
import org.mozilla.fenix.search.awesomebar.AwesomeBarView
import org.mozilla.fenix.search.toolbar.ToolbarInteractor
import org.mozilla.fenix.search.toolbar.ToolbarView
import org.mozilla.fenix.utils.Settings
class TempSearchInteractor(val onTextChangedCallback: (String) -> Unit) : ToolbarInteractor, AwesomeBarInteractor {
override fun onUrlCommitted(url: String) {
logDebug("boek", "onUrlCommitted $url")
}
override fun onEditingCanceled() {
logDebug("boek", "onEditingCanceled")
}
override fun onTextChanged(text: String) {
onTextChangedCallback.invoke(text)
}
override fun onUrlTapped(url: String) {
logDebug("boek", "onEditingCanceled")
}
override fun onSearchTermsTapped(searchTerms: String) {
logDebug("boek", "onEditingCanceled")
}
override fun onSearchShortcutEngineSelected(searchEngine: SearchEngine) {
logDebug("boek", "onEditingCanceled")
}
override fun onClickSearchEngineSettings() {
logDebug("boek", "onEditingCanceled")
}
override fun onExistingSessionSelected(session: Session) {
logDebug("boek", "onEditingCanceled")
}
override fun onExistingSessionSelected(tabId: String) {
logDebug("boek", "onEditingCanceled")
}
override fun onSearchShortcutsButtonClicked() {
logDebug("boek", "onEditingCanceled")
typealias SearchDialogFragmentStore = SearchFragmentStore
typealias SearchDialogInteractor = SearchInteractor
fun Settings.shouldShowSearchSuggestions(isPrivate: Boolean): Boolean {
return if (isPrivate) {
shouldShowSearchSuggestions && shouldShowSearchSuggestionsInPrivate
} else {
shouldShowSearchSuggestions
}
}
class SearchDialogFragment : AppCompatDialogFragment() {
private lateinit var interactor: SearchDialogInteractor
private lateinit var store: SearchDialogFragmentStore
private lateinit var toolbarView: ToolbarView
private lateinit var awesomeBarView: AwesomeBarView
private val tempInteractor = TempSearchInteractor {
view?.awesomeBar?.visibility = if (it.isEmpty()) View.INVISIBLE else View.VISIBLE
awesomeBarView.update(
SearchFragmentState(
query = it,
url = "",
searchTerms = "",
searchEngineSource = SearchEngineSource.Default(requireComponents.search.provider.getDefaultEngine(requireContext())),
defaultEngineSource = SearchEngineSource.Default(requireComponents.search.provider.getDefaultEngine(requireContext())),
showSearchSuggestions = true,
showSearchSuggestionsHint = false,
showSearchShortcuts = false,
areShortcutsAvailable = false,
showClipboardSuggestions = true,
showHistorySuggestions = true,
showBookmarkSuggestions = true,
tabId = null,
pastedText = null,
searchAccessPoint = null
)
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -103,10 +58,26 @@ class SearchDialogFragment : AppCompatDialogFragment() {
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_search_dialog, container, false)
store = SearchDialogFragmentStore(setUpState())
interactor = SearchDialogInteractor(
SearchDialogController(
activity = requireActivity() as HomeActivity,
sessionManager = requireComponents.core.sessionManager,
store = store,
navController = findNavController(),
settings = requireContext().settings(),
metrics = requireComponents.analytics.metrics,
clearToolbarFocus = {
toolbarView.view.hideKeyboard()
toolbarView.view.clearFocus()
}
)
)
toolbarView = ToolbarView(
requireContext(),
tempInteractor,
interactor,
null,
false,
view.toolbar,
@ -115,10 +86,60 @@ class SearchDialogFragment : AppCompatDialogFragment() {
awesomeBarView = AwesomeBarView(
requireContext(),
tempInteractor,
interactor,
view.awesomeBar
)
return view
}
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
consumeFrom(store) {
awesomeBar?.visibility = if (it.query.isEmpty()) View.INVISIBLE else View.VISIBLE
toolbarView.update(it)
awesomeBarView.update(it)
}
}
private fun setUpState(): SearchFragmentState {
val activity = activity as HomeActivity
val settings = activity.settings()
val args by navArgs<SearchDialogFragmentArgs>()
val tabId = args.sessionId
val tab = tabId?.let { requireComponents.core.store.state.findTab(it) }
val url = tab?.content?.url.orEmpty()
val currentSearchEngine = SearchEngineSource.Default(
requireComponents.search.provider.getDefaultEngine(requireContext())
)
val isPrivate = activity.browsingModeManager.mode.isPrivate
val areShortcutsAvailable =
requireContext().components.search.provider.installedSearchEngines(requireContext())
.list.size >= MINIMUM_SEARCH_ENGINES_NUMBER_TO_SHOW_SHORTCUTS
return SearchFragmentState(
query = url,
url = url,
searchTerms = tab?.content?.searchTerms.orEmpty(),
searchEngineSource = currentSearchEngine,
defaultEngineSource = currentSearchEngine,
showSearchSuggestions = settings.shouldShowSearchSuggestions(isPrivate),
showSearchSuggestionsHint = false,
showSearchShortcuts = settings.shouldShowSearchShortcuts &&
url.isEmpty() &&
areShortcutsAvailable,
areShortcutsAvailable = areShortcutsAvailable,
showClipboardSuggestions = settings.shouldShowClipboardSuggestions,
showHistorySuggestions = settings.shouldShowHistorySuggestions,
showBookmarkSuggestions = settings.shouldShowBookmarkSuggestions,
tabId = tabId,
pastedText = args.pastedText,
searchAccessPoint = args.searchAccessPoint
)
}
companion object {
private const val MINIMUM_SEARCH_ENGINES_NUMBER_TO_SHOW_SHORTCUTS = 2
}
}

View File

@ -83,6 +83,8 @@ class FenixTabsAdapter(
}
true
}
} else {
holder.itemView.setOnLongClickListener(null)
}
holder.itemView.setOnClickListener {

View File

@ -26,7 +26,7 @@
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:backgroundTint="?neutral"
android:inputType="text"
android:inputType="textCapSentences"
android:singleLine="true"
android:textAlignment="viewStart" />
</LinearLayout>

Some files were not shown because too many files have changed in this diff Show More