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