diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 87d873964e..d5c297efb5 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -49,6 +49,7 @@
# Possible startup regressions
*Application.kt @mozilla-mobile/Performance
*StrictMode*kt @mozilla-mobile/Performance
+*ConstraintLayoutPerfDetector.kt @mozilla-mobile/Performance
# We want to be aware of new features behind flags as well as features
# about to be enabled.
diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md
index 0de1259742..b9d0ec5da6 100644
--- a/.github/ISSUE_TEMPLATE/---bug-report.md
+++ b/.github/ISSUE_TEMPLATE/---bug-report.md
@@ -6,6 +6,9 @@ labels: "\U0001F41E bug"
assignees: ''
---
+[comment]: # (Please do your best to search for duplicate issues before filing a new issue so we can keep our issue board clean)
+[comment]: # (Every issue should have the exact bug and steps to reproduce described in it. Please do not file feedback list tickets as it is difficult to parse them and address their individual points)
+[comment]: # (Read https://github.com/mozilla-mobile/fenix#i-want-to-file-an-issue for more information)
## Steps to reproduce
@@ -17,3 +20,4 @@ assignees: ''
* Android device: ?
* Fenix version: ?
+
diff --git a/.github/ISSUE_TEMPLATE/---feature-request.md b/.github/ISSUE_TEMPLATE/---feature-request.md
index fdb6051711..52eb50f616 100644
--- a/.github/ISSUE_TEMPLATE/---feature-request.md
+++ b/.github/ISSUE_TEMPLATE/---feature-request.md
@@ -6,6 +6,10 @@ labels: "🌟 feature request"
assignees: ''
---
+[comment]: # (Please do your best to search for duplicate issues before filing a new issue so we can keep our issue board clean)
+[comment]: # (Every issue should have exactly one feature request described in it. Please do not file feedback list tickets as it is difficult to parse them and address their individual points)
+[comment]: # (Feature Requests are better when they’re open-ended instead of demanding a specific solution e.g: “I want an easier way to do X” instead of “add Y”)
+[comment]: # (Read https://github.com/mozilla-mobile/fenix#i-want-to-file-an-issue for more information)
### What is the user problem or growth opportunity you want to see solved?
diff --git a/.github/workflows/android-build-pr.yml b/.github/workflows/android-build-pr.yml
index 2c5f9175c7..96bfa3d2ab 100644
--- a/.github/workflows/android-build-pr.yml
+++ b/.github/workflows/android-build-pr.yml
@@ -16,6 +16,8 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 11
+ - name: Install Android SDK with pieces Gradle skips
+ run: ./automation/iceraven/install-sdk.sh
- name: Create version name
run: echo "VERSION_NAME=$(git describe --tags HEAD)" >> $GITHUB_ENV
- name: Build forkRelease variant of app
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
index dffdd9b511..afedfbd5c2 100644
--- a/.github/workflows/android-build.yml
+++ b/.github/workflows/android-build.yml
@@ -16,6 +16,8 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 11
+ - name: Install Android SDK with pieces Gradle skips
+ run: ./automation/iceraven/install-sdk.sh
- name: Create version name
run: echo "VERSION_NAME=$(git describe --tags HEAD)" >> $GITHUB_ENV
- name: Build forkRelease variant of app
diff --git a/.github/workflows/release-automation.yml b/.github/workflows/release-automation.yml
index 03505fefaf..fa873bdce8 100644
--- a/.github/workflows/release-automation.yml
+++ b/.github/workflows/release-automation.yml
@@ -15,6 +15,8 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 11
+ - name: Install Android SDK with pieces Gradle skips
+ run: ./automation/iceraven/install-sdk.sh
- name: Build forkRelease variant of app
uses: eskatos/gradle-command-action@v1
with:
diff --git a/.travis.yml b/.travis.yml
index cc4d361a53..66e5d32a5c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,14 @@
language: android
dist: trusty
script:
+ # Prepare SDK
- sudo mkdir -p /usr/local/android-sdk/licenses/
- sudo touch /usr/local/android-sdk/licenses/android-sdk-license
- echo "8933bad161af4178b1185d1a37fbf41ea5269c55" | sudo tee -a /usr/local/android-sdk/licenses/android-sdk-license
- echo "d56f5187479451eabf01fb78af6dfcb131a6481e" | sudo tee -a /usr/local/android-sdk/licenses/android-sdk-license
- echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" | sudo tee -a /usr/local/android-sdk/licenses/android-sdk-license
+ # The build needs this but Gradle refuses to fetch it.
+ - sdkmanager "ndk;21.0.6113669"
# Run tests
- ./gradlew -q testDebug 2>&1
# Make sure a release build builds
diff --git a/app/build.gradle b/app/build.gradle
index 1d620d4de6..5599417351 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -31,7 +31,7 @@ android {
resValue "bool", "IS_DEBUG", "false"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "false"
buildConfigField "String", "AMO_ACCOUNT", "\"mozilla\""
- buildConfigField "String", "AMO_COLLECTION", "\"83a9cccfe6e24a34bd7b155ff9ee32\""
+ buildConfigField "String", "AMO_COLLECTION", "\"7dfae8669acc4312a65e8ba5553036\""
def deepLinkSchemeValue = "fenix-dev"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = [
@@ -59,14 +59,12 @@ android {
shrinkResources false
minifyEnabled false
applicationIdSuffix ".fenix.debug"
- buildConfigField "String", "AMO_COLLECTION", "\"7dfae8669acc4312a65e8ba5553036\""
resValue "bool", "IS_DEBUG", "true"
pseudoLocalesEnabled true
}
nightly releaseTemplate >> {
applicationIdSuffix ".fenix"
buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
- buildConfigField "String", "AMO_COLLECTION", "\"7dfae8669acc4312a65e8ba5553036\""
def deepLinkSchemeValue = "fenix-nightly"
buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue]
@@ -199,6 +197,7 @@ android {
lintOptions {
lintConfig file("lint.xml")
+ baseline file("lint-baseline.xml")
}
packagingOptions {
@@ -355,7 +354,31 @@ ext.gleanGenerateMarkdownDocs = true
ext.gleanDocsDirectory = "$rootDir/docs"
apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"
+configurations {
+ // There's an interaction between Gradle's resolution of dependencies with different types
+ // (@jar, @aar) for `implementation` and `testImplementation` and with Android Studio's built-in
+ // JUnit test runner. The runtime classpath in the built-in JUnit test runner gets the
+ // dependency from the `implementation`, which is type @aar, and therefore the JNA dependency
+ // doesn't provide the JNI dispatch libraries in the correct Java resource directories. I think
+ // what's happening is that @aar type in `implementation` resolves to the @jar type in
+ // `testImplementation`, and that it wins the dependency resolution battle.
+ //
+ // A workaround is to add a new configuration which depends on the @jar type and to reference
+ // the underlying JAR file directly in `testImplementation`. This JAR file doesn't resolve to
+ // the @aar type in `implementation`. This works when invoked via `gradle`, but also sets the
+ // correct runtime classpath when invoked with Android Studio's built-in JUnit test runner.
+ // Success!
+ jnaForTest
+ // Robolectric, through `com.google.android.apps.common.testing.accessibility.framework`
+ // depends on an old version of protobuf that conflict with the Application Services one.
+ // See: https://github.com/mozilla/application-services/issues/2952
+ all*.exclude group: 'com.google.protobuf', module: 'protobuf-java'
+}
+
dependencies {
+ jnaForTest Deps.jna
+ testImplementation files(configurations.jnaForTest.copyRecursive().files)
+
debugImplementation Deps.mozilla_browser_engine_gecko_nightly
forkDebugImplementation Deps.mozilla_browser_engine_gecko_nightly
@@ -475,9 +498,6 @@ dependencies {
implementation Deps.androidx_work_ktx
implementation Deps.google_material
- implementation Deps.lottie
-
-
androidTestImplementation Deps.uiautomator
// Removed pending AndroidX fixes
androidTestImplementation "tools.fastlane:screengrab:2.0.0"
@@ -577,13 +597,13 @@ if (project.hasProperty("coverage")) {
// -------------------------------------------------------------------------------------------------
tasks.register('printVariants') {
doLast {
- def variants = android.applicationVariants.collect {[
- apks: it.variantData.outputScope.apkDatas.collect {[
- abi: it.filters.find { it.filterType == 'ABI' }.identifier,
- fileName: it.outputFileName,
- ]},
- build_type: it.buildType.name,
- name: it.name,
+ def variants = android.applicationVariants.collect { variant -> [
+ apks: variant.outputs.collect { output -> [
+ abi: output.getFilter(com.android.build.VariantOutput.FilterType.ABI),
+ fileName: output.outputFile.name
+ ]},
+ build_type: variant.buildType.name,
+ name: variant.name,
]}
// AndroidTest is a special case not included above
variants.add([
@@ -605,8 +625,8 @@ task buildTranslationArray {
foundLocales.append("new String[]{")
fileTree("src/main/res").visit { FileVisitDetails details ->
- if(details.file.path.endsWith("/strings.xml")){
- def languageCode = details.file.parent.tokenize('/').last().replaceAll('values-','').replaceAll('-r','-')
+ if(details.file.path.endsWith("${File.separator}strings.xml")){
+ def languageCode = details.file.parent.tokenize(File.separator).last().replaceAll('values-','').replaceAll('-r','-')
languageCode = (languageCode == "values") ? "en-US" : languageCode
foundLocales.append("\"").append(languageCode).append("\"").append(",")
}
diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
new file mode 100644
index 0000000000..a5aab8c1d5
--- /dev/null
+++ b/app/lint-baseline.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/lint.xml b/app/lint.xml
index aa99dd31f7..2c96b59b25 100644
--- a/app/lint.xml
+++ b/app/lint.xml
@@ -7,16 +7,58 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/metrics.yaml b/app/metrics.yaml
index bfe448679e..fd6de67c9a 100644
--- a/app/metrics.yaml
+++ b/app/metrics.yaml
@@ -279,7 +279,7 @@ events:
whats_new_tapped:
type: event
description: |
- A user opened the "what's new" page button
+ A user opened the "what's new" page button
bugs:
- https://github.com/mozilla-mobile/fenix/issues/5021
data_reviews:
@@ -947,6 +947,40 @@ metrics:
notification_emails:
- fenix-core@mozilla.com
expires: "2021-08-01"
+ close_tab_setting:
+ type: string
+ lifetime: application
+ description: |
+ A string that indicates the setting for tab closing:
+ MANUAL, ONE_DAY, ONE_WEEK, ONE_MONTH
+ send_in_pings:
+ - metrics
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/15347#issue-707408975
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/15811#issuecomment-706402952
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-08-01"
+ tab_view_setting:
+ type: string
+ lifetime: application
+ description: |
+ A string that indicates the setting for tab view:
+ GRID, LIST
+ send_in_pings:
+ - metrics
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/15347#issue-707408975
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/15811#issuecomment-706402952
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-08-01"
search_widget_installed:
type: boolean
lifetime: application
@@ -4207,3 +4241,18 @@ master_password:
notification_emails:
- fenix-core@mozilla.com
expires: "2021-03-01"
+
+tabs:
+ setting_opened:
+ type: event
+ description: |
+ The tab settings were opened.
+ bugs:
+ - https://github.com/mozilla-mobile/fenix/issues/15347#issue-707408975
+ data_reviews:
+ - https://github.com/mozilla-mobile/fenix/pull/15811#issuecomment-706402952
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - fenix-core@mozilla.com
+ expires: "2021-08-01"
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 6ca59c4d9e..aa2ae32aee 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -45,6 +45,14 @@
boolean ANDROID_DETECTED return true;
}
+####################################################################################################
+# Remove debug logs from release builds
+####################################################################################################
+-assumenosideeffects class android.util.Log {
+ public static boolean isLoggable(java.lang.String, int);
+ public static int d(...);
+}
+
####################################################################################################
# Mozilla Application Services
####################################################################################################
diff --git a/app/src/androidTest/java/org/mozilla/fenix/glean/BaselinePingTest.kt b/app/src/androidTest/java/org/mozilla/fenix/glean/BaselinePingTest.kt
index 8f3ddca9d8..1199adf351 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/glean/BaselinePingTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/glean/BaselinePingTest.kt
@@ -15,7 +15,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient
-import mozilla.components.concept.fetch.Client
import mozilla.components.service.glean.Glean
import mozilla.components.service.glean.config.Configuration
import mozilla.components.service.glean.net.ConceptFetchHttpUploader
@@ -78,7 +77,7 @@ class BaselinePingTest {
@JvmStatic
fun setupOnce() {
val httpClient = ConceptFetchHttpUploader(lazy {
- GeckoViewFetchClient(ApplicationProvider.getApplicationContext()) as Client
+ GeckoViewFetchClient(ApplicationProvider.getApplicationContext())
})
// Fenix does not initialize the Glean SDK in tests/debug builds, but this test
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt
index 3fb2195186..3e43e80b7f 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/SettingsBasicsTest.kt
@@ -10,9 +10,9 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
-import org.junit.Ignore
import org.mozilla.fenix.FenixApplication
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
@@ -85,7 +85,7 @@ class SettingsBasicsTest {
verifyThemes()
}.goBack {
}.openAccessibilitySubMenu {
- verifyMenuItems()
+ verifyAutomaticFontSizingMenuItems()
}.goBack {
// drill down to submenu
}
@@ -181,7 +181,7 @@ class SettingsBasicsTest {
}
@Test
- fun changeAccessibilitySettings() {
+ fun changeAccessibiltySettings() {
// Goes through the settings and changes the default text on a webpage, then verifies if the text has changed.
val fenixApp = activityIntentTestRule.activity.applicationContext as FenixApplication
val webpage = getLoremIpsumAsset(mockWebServer).url
@@ -193,7 +193,8 @@ class SettingsBasicsTest {
}.openThreeDotMenu {
}.openSettings {
}.openAccessibilitySubMenu {
- verifyMenuItems()
+ clickFontSizingSwitch()
+ verifyEnabledMenuItems()
changeTextSizeSlider(textSizePercentage)
verifyTextSizePercentage(textSizePercentage)
}.goBack {
@@ -201,6 +202,14 @@ class SettingsBasicsTest {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(webpage) {
checkTextSizeOnWebsite(textSizePercentage, fenixApp.components)
+ }.openTabDrawer {
+ }.openNewTab {
+ }.dismiss {
+ }.openThreeDotMenu {
+ }.openSettings {
+ }.openAccessibilitySubMenu {
+ clickFontSizingSwitch()
+ verifyMenuItemsAreDisabled()
}
}
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt
index 7ff893c418..bebecaba52 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/TabbedBrowsingTest.kt
@@ -311,8 +311,10 @@ class TabbedBrowsingTest {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openTabButtonShortcutsMenu {
}.openNewPrivateTabFromShortcutsMenu {
- verifyHomeScreen()
- verifyNavigationToolbar()
+ verifyKeyboardVisible()
+ verifyFocusedNavigationToolbar()
+ // dismiss search dialog
+ homeScreen { }.pressBack()
verifyHomePrivateBrowsingButton()
verifyHomeMenu()
verifyHomeWordmark()
@@ -326,8 +328,10 @@ class TabbedBrowsingTest {
}.openTabButtonShortcutsMenu {
}.openTabFromShortcutsMenu {
- verifyHomeScreen()
- verifyNavigationToolbar()
+ verifyKeyboardVisible()
+ verifyFocusedNavigationToolbar()
+ // dismiss search dialog
+ homeScreen { }.pressBack()
verifyHomeMenu()
verifyHomeWordmark()
verifyTabButton()
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt
index 8540e2a0a3..b9edac4a23 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt
@@ -25,6 +25,7 @@ import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
+import androidx.test.espresso.matcher.ViewMatchers.withHint
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
@@ -38,6 +39,7 @@ import androidx.test.uiautomator.Until.findObject
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.CoreMatchers.not
+import org.junit.Assert
import org.mozilla.fenix.R
import org.mozilla.fenix.components.Search
import org.mozilla.fenix.helpers.TestAssetHelper
@@ -52,6 +54,7 @@ import org.mozilla.fenix.helpers.withBitmapDrawable
*/
class HomeScreenRobot {
fun verifyNavigationToolbar() = assertNavigationToolbar()
+ fun verifyFocusedNavigationToolbar() = assertFocusedNavigationToolbar()
fun verifyHomeScreen() = assertHomeScreen()
fun verifyHomePrivateBrowsingButton() = assertHomePrivateBrowsingButton()
fun verifyHomeMenu() = assertHomeMenu()
@@ -64,6 +67,7 @@ class HomeScreenRobot {
fun verifyHomeComponent() = assertHomeComponent()
fun verifyDefaultSearchEngine(searchEngine: String) = verifySearchEngineIcon(searchEngine)
fun verifyNoTabsOpened() = assertNoTabsOpened()
+ fun verifyKeyboardVisible() = assertKeyboardVisibility(isExpectedToBeVisible = true)
// First Run elements
fun verifyWelcomeHeader() = assertWelcomeHeader()
@@ -387,6 +391,10 @@ class HomeScreenRobot {
.perform(click())
}
+ fun pressBack() {
+ onView(ViewMatchers.isRoot()).perform(ViewActions.pressBack())
+ }
+
fun openTabsListThreeDotMenu(interact: ThreeDotMenuMainRobot.() -> Unit): ThreeDotMenuMainRobot.Transition {
// tabsListThreeDotButton().perform(click())
@@ -481,6 +489,14 @@ fun homeScreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition
val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+private fun assertKeyboardVisibility(isExpectedToBeVisible: Boolean) =
+ Assert.assertEquals(
+ isExpectedToBeVisible,
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ .executeShellCommand("dumpsys input_method | grep mInputShown")
+ .contains("mInputShown=true")
+ )
+
private fun navigationToolbar() =
onView(allOf(withText("Search or enter address")))
@@ -490,6 +506,10 @@ private fun assertNavigationToolbar() =
onView(allOf(withText("Search or enter address")))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+private fun assertFocusedNavigationToolbar() =
+ onView(allOf(withHint("Search or enter address")))
+ .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+
private fun assertHomeScreen() = onView(ViewMatchers.withResourceName("homeLayout"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAccessibilityRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAccessibilityRobot.kt
index c02907f694..6402e65cb0 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAccessibilityRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/SettingsSubMenuAccessibilityRobot.kt
@@ -6,30 +6,32 @@
package org.mozilla.fenix.ui.robots
-import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.action.ViewActions.click
-import androidx.test.espresso.assertion.ViewAssertions.matches
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
import android.view.KeyEvent
-import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
-import android.view.KeyEvent.KEYCODE_DPAD_LEFT
import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.KEYCODE_DPAD_LEFT
+import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
import android.view.View
import android.widget.SeekBar
import android.widget.TextView
+import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewAssertion
-import org.hamcrest.CoreMatchers.allOf
-import androidx.test.espresso.matcher.ViewMatchers.withText
-import androidx.test.espresso.matcher.ViewMatchers.withId
-import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
-import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
+import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
+import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Matcher
import org.mozilla.fenix.components.Components
+import org.mozilla.fenix.helpers.assertIsEnabled
+import org.mozilla.fenix.helpers.isEnabled
import org.mozilla.fenix.ui.robots.SettingsSubMenuAccessibilityRobot.Companion.DECIMAL_CONVERSION
import org.mozilla.fenix.ui.robots.SettingsSubMenuAccessibilityRobot.Companion.MIN_VALUE
import org.mozilla.fenix.ui.robots.SettingsSubMenuAccessibilityRobot.Companion.STEP_SIZE
@@ -48,7 +50,13 @@ class SettingsSubMenuAccessibilityRobot {
const val TEXT_SIZE = 16f
}
- fun verifyMenuItems() = assertMenuItems()
+ fun verifyAutomaticFontSizingMenuItems() = assertAutomaticFontSizingMenuItems()
+
+ fun clickFontSizingSwitch() = toggleFontSizingSwitch()
+
+ fun verifyEnabledMenuItems() = assertEnabledMenuItems()
+
+ fun verifyMenuItemsAreDisabled() = assertMenuItemsAreDisabled()
fun changeTextSizeSlider(seekBarPercentage: Int) = adjustTextSizeSlider(seekBarPercentage)
@@ -69,7 +77,22 @@ class SettingsSubMenuAccessibilityRobot {
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-private fun assertMenuItems() {
+private fun assertAutomaticFontSizingMenuItems() {
+ onView(withText("Automatic font sizing"))
+ .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+ val strFont = "Font size will match your Android settings. Disable to manage font size here."
+ onView(withText(strFont))
+ .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+}
+
+private fun toggleFontSizingSwitch() {
+ // Toggle font size to off
+ onView(withText("Automatic font sizing"))
+ .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+ .perform(click())
+}
+
+private fun assertEnabledMenuItems() {
assertFontSize()
assertSliderBar()
}
@@ -77,9 +100,11 @@ private fun assertMenuItems() {
private fun assertFontSize() {
val view = onView(withText("Font Size"))
view.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+ .check(matches(isEnabled(true)))
val strFont = "Make text on websites larger or smaller"
onView(withText(strFont))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
+ .check(matches(isEnabled(true)))
}
private fun assertSliderBar() {
@@ -103,6 +128,20 @@ private fun assertTextSizePercentage(textSize: Int) {
.check(textSizePercentageEquals(textSize))
}
+private fun assertMenuItemsAreDisabled() {
+ onView(withText("Font Size")).assertIsEnabled(false)
+
+ val strFont = "Make text on websites larger or smaller"
+
+ onView(withText(strFont)).assertIsEnabled(false)
+
+ onView(withId(org.mozilla.fenix.R.id.sampleText)).assertIsEnabled(false)
+
+ onView(withId(org.mozilla.fenix.R.id.seekbar_value)).assertIsEnabled(false)
+
+ onView(withId(org.mozilla.fenix.R.id.seekbar)).assertIsEnabled(false)
+}
+
private fun goBackButton() =
onView(allOf(withContentDescription("Navigate up")))
diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt
index d77ab3513b..e28cd8ef91 100644
--- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt
+++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/ThreeDotMenuMainRobot.kt
@@ -14,6 +14,7 @@ import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.swipeDown
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
@@ -532,6 +533,6 @@ private fun assertOpenInAppButton() {
private fun addonsManagerButton() = onView(withText("Add-ons Manager"))
private fun clickAddonsManagerButton() {
- onView(withText("Add-ons")).click()
- addonsManagerButton().click()
+ onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown())
+ onView(withText("Add-ons")).check(matches(isCompletelyDisplayed())).click()
}
diff --git a/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt b/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt
index f230d142a2..eb13a10e49 100644
--- a/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt
+++ b/app/src/geckoBeta/java/org/mozilla/fenix/engine/GeckoProvider.kt
@@ -47,7 +47,7 @@ object GeckoProvider {
.build()
val settings = context.components.settings
- if (!settings.shouldUseAutoSize()) {
+ if (!settings.shouldUseAutoSize) {
runtimeSettings.automaticFontSizeAdjustment = false
val fontSize = settings.fontSizeFactor
runtimeSettings.fontSizeFactor = fontSize
diff --git a/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt b/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt
index bbfd5cf416..031c1c6c13 100644
--- a/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt
+++ b/app/src/geckoNightly/java/org/mozilla/fenix/engine/GeckoProvider.kt
@@ -47,7 +47,7 @@ object GeckoProvider {
.build()
val settings = context.components.settings
- if (!settings.shouldUseAutoSize()) {
+ if (!settings.shouldUseAutoSize) {
runtimeSettings.automaticFontSizeAdjustment = false
val fontSize = settings.fontSizeFactor
runtimeSettings.fontSizeFactor = fontSize
diff --git a/app/src/geckoRelease/java/org/mozilla/fenix/engine/GeckoProvider.kt b/app/src/geckoRelease/java/org/mozilla/fenix/engine/GeckoProvider.kt
index 1740a26efc..a01593bb7c 100644
--- a/app/src/geckoRelease/java/org/mozilla/fenix/engine/GeckoProvider.kt
+++ b/app/src/geckoRelease/java/org/mozilla/fenix/engine/GeckoProvider.kt
@@ -57,7 +57,7 @@ object GeckoProvider {
.build()
val settings = context.components.settings
- if (!settings.shouldUseAutoSize()) {
+ if (!settings.shouldUseAutoSize) {
runtimeSettings.automaticFontSizeAdjustment = false
val fontSize = settings.fontSizeFactor
runtimeSettings.fontSizeFactor = fontSize
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 78a517e815..6c4d1e1cf4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -229,9 +229,6 @@
-
-
diff --git a/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonInstallationDialogFragment.kt b/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonInstallationDialogFragment.kt
index 8ce89ec234..de08c70a5f 100644
--- a/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonInstallationDialogFragment.kt
+++ b/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonInstallationDialogFragment.kt
@@ -53,6 +53,10 @@ private const val DEFAULT_VALUE = Int.MAX_VALUE
/**
* A dialog that shows [Addon] installation confirmation.
*/
+// We have an extra "Lint" Android Studio linter pass that Android Components
+// where the original code came from doesn't. So we tell it to ignore us. Make
+// sure to keep up with changes in Android Components though.
+@SuppressLint("all")
class PagedAddonInstallationDialogFragment : AppCompatDialogFragment() {
private val scope = CoroutineScope(Dispatchers.IO)
@VisibleForTesting internal var iconJob: Job? = null
diff --git a/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonsManagerAdapter.kt b/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonsManagerAdapter.kt
index 0e2355e5c7..a6f319f2b6 100644
--- a/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonsManagerAdapter.kt
+++ b/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonsManagerAdapter.kt
@@ -58,6 +58,10 @@ private const val VIEW_HOLDER_TYPE_ADDON = 2
* @property style Indicates how items should look like.
*/
@Suppress("TooManyFunctions", "LargeClass")
+// We have an extra "Lint" Android Studio linter pass that Android Components
+// where the original code came from doesn't. So we tell it to ignore us. Make
+// sure to keep up with changes in Android Components though.
+@SuppressLint("all")
class PagedAddonsManagerAdapter(
private val addonCollectionProvider: PagedAddonCollectionProvider,
private val addonsManagerDelegate: AddonsManagerAdapterDelegate,
diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
index 02060508ee..553baa2ba7 100644
--- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
+++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
@@ -21,11 +21,6 @@ object FeatureFlags {
*/
val syncedTabsInTabsTray = Config.channel.isNightlyOrDebug
- /**
- * Enables showing the top frequently visited sites
- */
- const val topFrecentSite = true
-
/**
* Shows the grid view settings for the tabs tray.
*/
@@ -50,4 +45,9 @@ object FeatureFlags {
* Enables ETP cookie purging
*/
val etpCookiePurging = Config.channel.isNightlyOrDebug
+
+ /**
+ * Returns user to browser on cold start if they have open tabs
+ */
+ val returnToBrowserOnColdStart = Config.channel.isNightlyOrDebug
}
diff --git a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
index 3ea661a6cf..d492e9b56c 100644
--- a/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
+++ b/app/src/main/java/org/mozilla/fenix/FenixApplication.kt
@@ -32,7 +32,6 @@ import mozilla.components.service.glean.config.Configuration
import mozilla.components.service.glean.net.ConceptFetchHttpUploader
import mozilla.components.support.base.log.Log
import mozilla.components.support.base.log.logger.Logger
-import mozilla.components.support.base.log.sink.AndroidLogSink
import mozilla.components.support.ktx.android.content.isMainProcess
import mozilla.components.support.ktx.android.content.runOnlyInMainProcess
import mozilla.components.support.locale.LocaleAwareApplication
@@ -42,7 +41,6 @@ import mozilla.components.support.utils.logElapsedTime
import mozilla.components.support.webextensions.WebExtensionSupport
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.metrics.MetricServiceType
-import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.StorageStatsMetrics
import org.mozilla.fenix.perf.StartupTimeline
@@ -114,7 +112,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
setupCrashReporting()
// We want the log messages of all builds to go to Android logcat
- Log.addSink(AndroidLogSink())
+ Log.addSink(FenixLogSink(logsDebug = Config.channel.isDebug))
}
@CallSuper
diff --git a/app/src/main/java/org/mozilla/fenix/FenixLogSink.kt b/app/src/main/java/org/mozilla/fenix/FenixLogSink.kt
new file mode 100644
index 0000000000..b09b003af4
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/FenixLogSink.kt
@@ -0,0 +1,30 @@
+/* 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
+
+import mozilla.components.support.base.log.Log
+import mozilla.components.support.base.log.sink.AndroidLogSink
+import mozilla.components.support.base.log.sink.LogSink
+
+/**
+ * Fenix [LogSink] implementation that writes to Android's log, depending on settings.
+ *
+ * @param logsDebug If set to false, removes logging of debug logs.
+ */
+class FenixLogSink(private val logsDebug: Boolean = true) : LogSink {
+
+ private val androidLogSink = AndroidLogSink()
+
+ override fun log(
+ priority: Log.Priority,
+ tag: String?,
+ throwable: Throwable?,
+ message: String?
+ ) {
+ if (priority == Log.Priority.DEBUG && !logsDebug)
+ return
+ androidLogSink.log(priority, tag, throwable, message)
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
index 1e99001e62..341ed4249e 100644
--- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
+++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
@@ -38,7 +38,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import mozilla.components.browser.search.SearchEngine
-import mozilla.components.browser.session.SessionManager
+import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.WebExtensionState
import mozilla.components.concept.engine.EngineSession
@@ -61,7 +61,6 @@ import mozilla.components.support.webextensions.WebExtensionPopupFeature
import org.mozilla.fenix.GleanMetrics.Metrics
import org.mozilla.fenix.addons.AddonDetailsFragmentDirections
import org.mozilla.fenix.addons.AddonPermissionsDetailsFragmentDirections
-import org.mozilla.fenix.browser.UriOpenedObserver
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
@@ -122,11 +121,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
private var webExtScope: CoroutineScope? = null
lateinit var themeManager: ThemeManager
lateinit var browsingModeManager: BrowsingModeManager
- private lateinit var sessionObserver: SessionManager.Observer
private var isVisuallyComplete = false
- private var privateNotificationObserver: PrivateNotificationFeature? = null
+ private var privateNotificationObserver: PrivateNotificationFeature? =
+ null
private var isToolbarInflated = false
@@ -181,8 +180,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
.attachViewToRunVisualCompletenessQueueLater(WeakReference(rootContainer))
}
- sessionObserver = UriOpenedObserver(this)
-
checkPrivateShortcutEntryPoint(intent)
privateNotificationObserver = PrivateNotificationFeature(
applicationContext,
@@ -192,14 +189,18 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
it.start()
}
- if (isActivityColdStarted(intent, savedInstanceState)) {
- externalSourceIntentProcessors.any {
+ if (isActivityColdStarted(
+ intent,
+ savedInstanceState
+ ) && !externalSourceIntentProcessors.any {
it.process(
intent,
navHost.navController,
this.intent
)
}
+ ) {
+ navigateToBrowserOnColdStart()
}
Performance.processIntentIfPerformanceTest(intent, this)
@@ -240,7 +241,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE.
}
- protected open fun startupTelemetryOnCreateCalled(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) {
+ protected open fun startupTelemetryOnCreateCalled(
+ safeIntent: SafeIntent,
+ hasSavedInstanceState: Boolean
+ ) {
components.appStartupTelemetry.onHomeActivityOnCreate(
safeIntent,
hasSavedInstanceState,
@@ -322,10 +326,18 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
final override fun onPause() {
+ // We should return to the browser if there were normal tabs when we left the app
+ settings().shouldReturnToBrowser =
+ components.core.store.state.getNormalOrPrivateTabs(private = false).isNotEmpty()
+
if (settings().lastKnownMode.isPrivate) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
+ // We will remove this when AC code lands to emit a fact on getTopSites in DefaultTopSitesStorage
+ // https://github.com/mozilla-mobile/android-components/issues/8679
+ settings().topSitesSize = components.core.topSitesStorage.cachedTopSites.size
+
super.onPause()
// Diagnostic breadcrumb for "Display already aquired" crash:
@@ -763,6 +775,17 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
}
+ open fun navigateToBrowserOnColdStart() {
+ // Normal tabs + cold start -> Should go back to browser if we had any tabs open when we left last
+ // except for PBM + Cold Start there won't be any tabs since they're evicted so we never will navigate
+ if (FeatureFlags.returnToBrowserOnColdStart &&
+ settings().shouldReturnToBrowser &&
+ !browsingModeManager.mode.isPrivate
+ ) {
+ openToBrowser(BrowserDirection.FromGlobal, null)
+ }
+ }
+
override fun attachBaseContext(base: Context) {
base.components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
super.attachBaseContext(base)
diff --git a/app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt b/app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt
new file mode 100644
index 0000000000..88904292f7
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/TelemetryMiddleware.kt
@@ -0,0 +1,106 @@
+/* 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
+
+import androidx.annotation.VisibleForTesting
+import mozilla.components.browser.state.action.BrowserAction
+import mozilla.components.browser.state.action.ContentAction
+import mozilla.components.browser.state.action.TabListAction
+import mozilla.components.browser.state.selector.findTab
+import mozilla.components.browser.state.selector.normalTabs
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+import mozilla.components.support.base.log.logger.Logger
+import org.mozilla.fenix.components.metrics.Event
+import org.mozilla.fenix.components.metrics.MetricController
+import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
+import org.mozilla.fenix.utils.Settings
+
+/**
+ * [Middleware] to record telemetry in response to [BrowserAction]s.
+ *
+ * @property settings reference to the application [Settings].
+ * @property adsTelemetry reference to [AdsTelemetry] use to record search telemetry.
+ * @property metrics reference to the configured [MetricController] to record general page load events.
+ */
+class TelemetryMiddleware(
+ private val settings: Settings,
+ private val adsTelemetry: AdsTelemetry,
+ private val metrics: MetricController
+) : Middleware {
+
+ private val logger = Logger("TelemetryMiddleware")
+
+ @VisibleForTesting
+ internal val redirectChains = mutableMapOf()
+
+ /**
+ * Utility to collect URLs / load requests in between location changes.
+ */
+ internal class RedirectChain(internal val root: String) {
+ internal val chain = mutableListOf()
+
+ fun add(url: String) {
+ chain.add(url)
+ }
+ }
+
+ @Suppress("TooGenericExceptionCaught")
+ override fun invoke(
+ context: MiddlewareContext,
+ next: (BrowserAction) -> Unit,
+ action: BrowserAction
+ ) {
+ // Pre process actions
+ when (action) {
+ is ContentAction.UpdateLoadingStateAction -> {
+ context.state.findTab(action.sessionId)?.let { tab ->
+ // Record UriOpened event when a non-private page finishes loading
+ if (tab.content.loading && !action.loading && !tab.content.private) {
+ metrics.track(Event.UriOpened)
+ }
+ }
+ }
+ is ContentAction.UpdateLoadRequestAction -> {
+ context.state.findTab(action.sessionId)?.let { tab ->
+ // Collect all load requests in between location changes
+ if (!redirectChains.containsKey(action.sessionId) && action.loadRequest.url != tab.content.url) {
+ redirectChains[action.sessionId] = RedirectChain(tab.content.url)
+ }
+
+ redirectChains[action.sessionId]?.add(action.loadRequest.url)
+ }
+ }
+ is ContentAction.UpdateUrlAction -> {
+ redirectChains[action.sessionId]?.let {
+ // Record ads telemetry providing all redirects
+ try {
+ adsTelemetry.trackAdClickedMetric(it.root, it.chain)
+ } catch (t: Throwable) {
+ logger.info("Failed to record search telemetry", t)
+ } finally {
+ redirectChains.remove(action.sessionId)
+ }
+ }
+ }
+ }
+
+ next(action)
+
+ // Post process actions
+ when (action) {
+ is TabListAction.AddTabAction,
+ is TabListAction.AddMultipleTabsAction,
+ is TabListAction.RemoveTabAction,
+ is TabListAction.RemoveAllNormalTabsAction,
+ is TabListAction.RemoveAllTabsAction,
+ is TabListAction.RestoreAction -> {
+ // Update/Persist tabs count whenever it changes
+ settings.openTabsCount = context.state.normalTabs.count()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
index 97d85f1970..151e83eeec 100644
--- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
@@ -32,6 +32,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -41,6 +42,7 @@ import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab
+import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.content.DownloadState
import mozilla.components.browser.state.store.BrowserStore
@@ -190,6 +192,28 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
val view = inflater.inflate(R.layout.fragment_browser, container, false)
val activity = activity as HomeActivity
+ components = requireComponents
+
+ if (customTabSessionId == null) {
+ // Once tab restoration is complete, if there are no tabs to show in the browser, go home
+ components.core.store.flowScoped(viewLifecycleOwner) { flow ->
+ flow.map { state -> state.restoreComplete }
+ .ifChanged()
+ .collect { restored ->
+ if (restored) {
+ val tabs =
+ components.core.store.state.getNormalOrPrivateTabs(
+ activity.browsingModeManager.mode.isPrivate
+ )
+ if (tabs.isEmpty()) findNavController().popBackStack(
+ R.id.homeFragment,
+ false
+ )
+ }
+ }
+ }
+ }
+
activity.themeManager.applyStatusBarTheme(activity)
browserFragmentStore = StoreProvider.get(this) {
@@ -198,8 +222,6 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
)
}
- components = requireComponents
-
return view
}
diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
index 1642afe09d..ebe364c3fe 100644
--- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt
@@ -11,7 +11,6 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources
-import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
@@ -86,7 +85,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
val readerModeAction =
BrowserToolbar.ToggleButton(
- image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_readermode)!!,
+ image = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_readermode)!!,
imageSelected =
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_readermode_selected)!!,
contentDescription = requireContext().getString(R.string.browser_menu_read),
diff --git a/app/src/main/java/org/mozilla/fenix/browser/TelemetrySessionObserver.kt b/app/src/main/java/org/mozilla/fenix/browser/TelemetrySessionObserver.kt
deleted file mode 100644
index 46733f0797..0000000000
--- a/app/src/main/java/org/mozilla/fenix/browser/TelemetrySessionObserver.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-/* 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.browser
-
-import androidx.annotation.VisibleForTesting
-import mozilla.components.browser.session.Session
-import org.mozilla.fenix.components.metrics.Event
-import org.mozilla.fenix.components.metrics.MetricController
-import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
-
-class TelemetrySessionObserver(
- private val metrics: MetricController,
- private val ads: AdsTelemetry
-) : Session.Observer {
- private var urlLoading: String? = null
- @VisibleForTesting
- var redirectChain = mutableListOf()
- @VisibleForTesting
- var originSessionUrl: String? = null
-
- private val temporaryFix = TemporaryFix()
-
- override fun onLoadingStateChanged(session: Session, loading: Boolean) {
- if (loading) {
- urlLoading = session.url
- } else if (urlLoading != null && !session.private && temporaryFix.shouldSendEvent(session.url)) {
- temporaryFix.eventSentFor = session.url
- metrics.track(Event.UriOpened)
- }
- }
-
- /**
- * When a link is clicked, record its redirect chain as well as origin url
- */
- override fun onLoadRequest(
- session: Session,
- url: String,
- triggeredByRedirect: Boolean,
- triggeredByWebContent: Boolean
- ) {
- if (isFirstLinkInRedirectChain(url, session.url)) {
- originSessionUrl = session.url
- }
- if (canStartChain()) {
- redirectChain.add(url)
- }
- }
-
- private fun canStartChain(): Boolean {
- return originSessionUrl != null
- }
-
- private fun isFirstLinkInRedirectChain(url: String, sessionUrl: String): Boolean {
- return originSessionUrl == null && url != sessionUrl
- }
-
- /**
- * After the redirect chain has finished, check if we encountered an ad on the way and clear
- * the stored info for that chain
- */
- override fun onUrlChanged(session: Session, url: String) {
- ads.trackAdClickedMetric(originSessionUrl, redirectChain)
- originSessionUrl = null
- redirectChain.clear()
- }
-
- /**
- * Currently, [Session.Observer.onLoadingStateChanged] is called multiple times the first
- * time a new session loads a page. This is inflating our telemetry numbers, so we need to
- * handle it, but we will be able to remove this code when [onLoadingStateChanged] has
- * been fixed.
- *
- * See Fenix #3676
- * See AC https://github.com/mozilla-mobile/android-components/issues/4795
- * TODO remove this class after AC #4795 has been fixed
- */
- private class TemporaryFix {
- var eventSentFor: String? = null
-
- fun shouldSendEvent(newUrl: String): Boolean = eventSentFor != newUrl
- }
-}
diff --git a/app/src/main/java/org/mozilla/fenix/browser/UriOpenedObserver.kt b/app/src/main/java/org/mozilla/fenix/browser/UriOpenedObserver.kt
deleted file mode 100644
index 3c42b1f1ef..0000000000
--- a/app/src/main/java/org/mozilla/fenix/browser/UriOpenedObserver.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/* 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.browser
-
-import androidx.annotation.VisibleForTesting
-import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.LifecycleOwner
-import mozilla.components.browser.session.Session
-import mozilla.components.browser.session.SessionManager
-import org.mozilla.fenix.components.metrics.MetricController
-import org.mozilla.fenix.ext.components
-import org.mozilla.fenix.ext.metrics
-import org.mozilla.fenix.ext.sessionsOfType
-import org.mozilla.fenix.ext.settings
-import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
-import org.mozilla.fenix.utils.Settings
-
-class UriOpenedObserver(
- private val settings: Settings,
- private val owner: LifecycleOwner,
- private val sessionManager: SessionManager,
- metrics: MetricController,
- ads: AdsTelemetry
-) : SessionManager.Observer {
-
- constructor(activity: FragmentActivity) : this(
- activity.applicationContext.settings(),
- activity,
- activity.components.core.sessionManager,
- activity.metrics,
- activity.components.core.adsTelemetry
- )
-
- @VisibleForTesting
- internal val singleSessionObserver = TelemetrySessionObserver(metrics, ads)
-
- init {
- sessionManager.register(this, owner)
- sessionManager.selectedSession?.register(singleSessionObserver, owner)
- }
-
- override fun onSessionSelected(session: Session) {
- session.register(singleSessionObserver, owner)
- }
-
- private fun saveOpenTabsCount() {
- settings.setOpenTabsCount(sessionManager.sessionsOfType(private = false).count())
- }
-
- override fun onAllSessionsRemoved() {
- saveOpenTabsCount()
- sessionManager.sessions.forEach {
- it.unregister(singleSessionObserver)
- }
- }
-
- override fun onSessionAdded(session: Session) {
- saveOpenTabsCount()
- session.register(singleSessionObserver, owner)
- }
-
- override fun onSessionRemoved(session: Session) {
- saveOpenTabsCount()
- session.unregister(singleSessionObserver)
- }
-
- override fun onSessionsRestored() {
- saveOpenTabsCount()
- sessionManager.sessions.forEach {
- it.register(singleSessionObserver, owner)
- }
- }
-}
diff --git a/app/src/main/java/org/mozilla/fenix/browser/readermode/ReaderModeController.kt b/app/src/main/java/org/mozilla/fenix/browser/readermode/ReaderModeController.kt
index 4f1de82d04..478f5dc89a 100644
--- a/app/src/main/java/org/mozilla/fenix/browser/readermode/ReaderModeController.kt
+++ b/app/src/main/java/org/mozilla/fenix/browser/readermode/ReaderModeController.kt
@@ -7,7 +7,7 @@ package org.mozilla.fenix.browser.readermode
import android.view.View
import android.widget.Button
import android.widget.RadioButton
-import androidx.core.content.ContextCompat
+import androidx.appcompat.content.res.AppCompatResources
import mozilla.components.feature.readerview.ReaderViewFeature
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.R
@@ -54,7 +54,7 @@ class DefaultReaderModeController(
findViewById