Merge tag 'v110.0.1' into fork

fork iceraven-1.21.0
akliuxingyuan 1 year ago
parent ef4366af31
commit e717d79abd

@ -36,4 +36,6 @@ jobs:
type: decision-task type: decision-task
treeherder-symbol: legacy-api-ui treeherder-symbol: legacy-api-ui
target-tasks-method: legacy_api_ui_tests target-tasks-method: legacy_api_ui_tests
when: [] # temporarily unscheduled when:
- {hour: 11, minute: 0}
- {hour: 20, minute: 0}

@ -37,6 +37,9 @@ messaging:
messages: messages:
type: json type: json
description: A growable collection of messages description: A growable collection of messages
notification-config:
type: json
description: Configuration of the notification worker for all notification messages.
on-control: on-control:
type: string type: string
description: What should be displayed when a control message is selected. description: What should be displayed when a control message is selected.
@ -71,6 +74,14 @@ nimbus-validation:
settings-title: settings-title:
type: string type: string
description: The title of displayed in the Settings screen and app menu. description: The title of displayed in the Settings screen and app menu.
pre-permission-notification-prompt:
description: A feature that shows the pre-permission notification prompt.
hasExposure: true
exposureDescription: ""
variables:
enabled:
type: boolean
description: "if true, the pre-permission notification prompt is shown to the user."
re-engagement-notification: re-engagement-notification:
description: A feature that shows the re-enagement notification if the user is inactive. description: A feature that shows the re-enagement notification if the user is inactive.
hasExposure: true hasExposure: true

@ -3,7 +3,7 @@ on: [pull_request]
jobs: jobs:
run-build: run-build:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'MickeyMoz' if: ${{ false }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -108,7 +108,6 @@ jobs:
run-ui: run-ui:
runs-on: macos-11 runs-on: macos-11
if: ${{ false }} if: ${{ false }}
timeout-minutes: 60 timeout-minutes: 60
strategy: strategy:
matrix: matrix:

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: "Update Android-Components" - name: "Update Android-Components"
uses: mozilla-mobile/relbot@master uses: mozilla-mobile/relbot@5.0.2
if: github.repository == 'mozilla-mobile/fenix' if: github.repository == 'mozilla-mobile/fenix'
with: with:
project: fenix project: fenix

@ -47,7 +47,7 @@ android {
buildConfigField "String", "GIT_HASH", "\"\"" // see override in release builds for why it's blank. buildConfigField "String", "GIT_HASH", "\"\"" // see override in release builds for why it's blank.
// This should be the "public" base URL of AMO. // This should be the "public" base URL of AMO.
buildConfigField "String", "AMO_BASE_URL", "\"https://addons.mozilla.org\"" buildConfigField "String", "AMO_BASE_URL", "\"https://addons.mozilla.org\""
buildConfigField "String", "AMO_COLLECTION_NAME", "\"7dfae8669acc4312a65e8ba5553036\"" buildConfigField "String", "AMO_COLLECTION_NAME", "\"Extensions-for-Android\""
buildConfigField "String", "AMO_COLLECTION_USER", "\"mozilla\"" buildConfigField "String", "AMO_COLLECTION_USER", "\"mozilla\""
// These add-ons should be excluded for Mozilla Online builds. // These add-ons should be excluded for Mozilla Online builds.
buildConfigField "String[]", "MOZILLA_ONLINE_ADDON_EXCLUSIONS", buildConfigField "String[]", "MOZILLA_ONLINE_ADDON_EXCLUSIONS",
@ -59,7 +59,8 @@ android {
"\"foxyproxy@eric.h.jung\"," + "\"foxyproxy@eric.h.jung\"," +
"\"{73a6fe31-595d-460b-a920-fcc0f8843232}\"," + "\"{73a6fe31-595d-460b-a920-fcc0f8843232}\"," +
"\"jid1-BoFifL9Vbdl2zQ@jetpack\"," + "\"jid1-BoFifL9Vbdl2zQ@jetpack\"," +
"\"woop-NoopscooPsnSXQ@jetpack\"" + "\"woop-NoopscooPsnSXQ@jetpack\"," +
"\"adnauseam@rednoise.org\"" +
"}" "}"
// This should be the base URL used to call the AMO API. // This should be the base URL used to call the AMO API.
buildConfigField "String", "AMO_SERVER_URL", "\"https://services.addons.mozilla.org\"" buildConfigField "String", "AMO_SERVER_URL", "\"https://services.addons.mozilla.org\""
@ -260,7 +261,7 @@ android {
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion = Versions.androidx_compose_compiler kotlinCompilerExtensionVersion = FenixVersions.androidx_compose_compiler
} }
} }
@ -506,150 +507,149 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
} }
dependencies { dependencies {
jnaForTest Deps.jna jnaForTest FenixDependencies.jna
testImplementation files(configurations.jnaForTest.copyRecursive().files) testImplementation files(configurations.jnaForTest.copyRecursive().files)
implementation Deps.mozilla_browser_engine_gecko implementation FenixDependencies.mozilla_browser_engine_gecko
implementation Deps.kotlin_stdlib implementation FenixDependencies.kotlin_stdlib
implementation Deps.kotlin_coroutines implementation FenixDependencies.kotlin_coroutines
implementation Deps.kotlin_coroutines_android implementation FenixDependencies.kotlin_coroutines_android
testImplementation Deps.kotlin_coroutines_test testImplementation FenixDependencies.kotlin_coroutines_test
implementation Deps.androidx_appcompat implementation FenixDependencies.androidx_appcompat
implementation Deps.androidx_constraintlayout implementation FenixDependencies.androidx_constraintlayout
implementation Deps.androidx_coordinatorlayout implementation FenixDependencies.androidx_coordinatorlayout
implementation Deps.google_accompanist_drawablepainter implementation FenixDependencies.google_accompanist_drawablepainter
implementation Deps.google_accompanist_insets implementation FenixDependencies.google_accompanist_insets
implementation Deps.sentry implementation FenixDependencies.sentry
implementation Deps.mozilla_compose_awesomebar implementation FenixDependencies.mozilla_compose_awesomebar
implementation Deps.mozilla_concept_awesomebar implementation FenixDependencies.mozilla_concept_awesomebar
implementation Deps.mozilla_concept_base implementation FenixDependencies.mozilla_concept_base
implementation Deps.mozilla_concept_engine implementation FenixDependencies.mozilla_concept_engine
implementation Deps.mozilla_concept_menu implementation FenixDependencies.mozilla_concept_menu
implementation Deps.mozilla_concept_push implementation FenixDependencies.mozilla_concept_push
implementation Deps.mozilla_concept_storage implementation FenixDependencies.mozilla_concept_storage
implementation Deps.mozilla_concept_sync implementation FenixDependencies.mozilla_concept_sync
implementation Deps.mozilla_concept_toolbar implementation FenixDependencies.mozilla_concept_toolbar
implementation Deps.mozilla_concept_tabstray implementation FenixDependencies.mozilla_concept_tabstray
implementation Deps.mozilla_browser_domains implementation FenixDependencies.mozilla_browser_domains
implementation Deps.mozilla_browser_icons implementation FenixDependencies.mozilla_browser_icons
implementation Deps.mozilla_browser_menu implementation FenixDependencies.mozilla_browser_menu
implementation Deps.mozilla_browser_menu2 implementation FenixDependencies.mozilla_browser_menu2
implementation Deps.mozilla_browser_session_storage implementation FenixDependencies.mozilla_browser_session_storage
implementation Deps.mozilla_browser_state implementation FenixDependencies.mozilla_browser_state
implementation Deps.mozilla_browser_storage_sync implementation FenixDependencies.mozilla_browser_storage_sync
implementation Deps.mozilla_browser_tabstray implementation FenixDependencies.mozilla_browser_tabstray
implementation Deps.mozilla_browser_thumbnails implementation FenixDependencies.mozilla_browser_thumbnails
implementation Deps.mozilla_browser_toolbar implementation FenixDependencies.mozilla_browser_toolbar
implementation Deps.mozilla_feature_addons implementation FenixDependencies.mozilla_feature_addons
implementation Deps.mozilla_feature_accounts implementation FenixDependencies.mozilla_feature_accounts
implementation Deps.mozilla_feature_app_links implementation FenixDependencies.mozilla_feature_app_links
implementation Deps.mozilla_feature_autofill implementation FenixDependencies.mozilla_feature_autofill
implementation Deps.mozilla_feature_awesomebar implementation FenixDependencies.mozilla_feature_awesomebar
implementation Deps.mozilla_feature_contextmenu implementation FenixDependencies.mozilla_feature_contextmenu
implementation Deps.mozilla_feature_customtabs implementation FenixDependencies.mozilla_feature_customtabs
implementation Deps.mozilla_feature_downloads implementation FenixDependencies.mozilla_feature_downloads
implementation Deps.mozilla_feature_intent implementation FenixDependencies.mozilla_feature_intent
implementation Deps.mozilla_feature_media implementation FenixDependencies.mozilla_feature_media
implementation Deps.mozilla_feature_prompts implementation FenixDependencies.mozilla_feature_prompts
implementation Deps.mozilla_feature_push implementation FenixDependencies.mozilla_feature_push
implementation Deps.mozilla_feature_privatemode implementation FenixDependencies.mozilla_feature_privatemode
implementation Deps.mozilla_feature_pwa implementation FenixDependencies.mozilla_feature_pwa
implementation Deps.mozilla_feature_qr implementation FenixDependencies.mozilla_feature_qr
implementation Deps.mozilla_feature_search implementation FenixDependencies.mozilla_feature_search
implementation Deps.mozilla_feature_session implementation FenixDependencies.mozilla_feature_session
implementation Deps.mozilla_feature_syncedtabs implementation FenixDependencies.mozilla_feature_syncedtabs
implementation Deps.mozilla_feature_toolbar implementation FenixDependencies.mozilla_feature_toolbar
implementation Deps.mozilla_feature_tabs implementation FenixDependencies.mozilla_feature_tabs
implementation Deps.mozilla_feature_findinpage implementation FenixDependencies.mozilla_feature_findinpage
implementation Deps.mozilla_feature_logins implementation FenixDependencies.mozilla_feature_logins
implementation Deps.mozilla_feature_site_permissions implementation FenixDependencies.mozilla_feature_site_permissions
implementation Deps.mozilla_feature_readerview implementation FenixDependencies.mozilla_feature_readerview
implementation Deps.mozilla_feature_tab_collections implementation FenixDependencies.mozilla_feature_tab_collections
implementation Deps.mozilla_feature_recentlyclosed implementation FenixDependencies.mozilla_feature_recentlyclosed
implementation Deps.mozilla_feature_top_sites implementation FenixDependencies.mozilla_feature_top_sites
implementation Deps.mozilla_feature_share implementation FenixDependencies.mozilla_feature_share
implementation Deps.mozilla_feature_accounts_push implementation FenixDependencies.mozilla_feature_accounts_push
implementation Deps.mozilla_feature_webauthn implementation FenixDependencies.mozilla_feature_webauthn
implementation Deps.mozilla_feature_webcompat implementation FenixDependencies.mozilla_feature_webcompat
implementation Deps.mozilla_feature_webnotifications implementation FenixDependencies.mozilla_feature_webnotifications
implementation Deps.mozilla_feature_webcompat_reporter implementation FenixDependencies.mozilla_feature_webcompat_reporter
implementation Deps.mozilla_service_pocket implementation FenixDependencies.mozilla_service_pocket
implementation Deps.mozilla_service_contile implementation FenixDependencies.mozilla_service_contile
implementation Deps.mozilla_service_digitalassetlinks implementation FenixDependencies.mozilla_service_digitalassetlinks
implementation Deps.mozilla_service_sync_autofill implementation FenixDependencies.mozilla_service_sync_autofill
implementation Deps.mozilla_service_sync_logins implementation FenixDependencies.mozilla_service_sync_logins
implementation Deps.mozilla_service_firefox_accounts implementation FenixDependencies.mozilla_service_firefox_accounts
implementation(Deps.mozilla_service_glean) implementation(FenixDependencies.mozilla_service_glean)
implementation Deps.mozilla_service_location implementation FenixDependencies.mozilla_service_location
implementation Deps.mozilla_service_nimbus implementation FenixDependencies.mozilla_service_nimbus
implementation Deps.mozilla_support_extensions implementation FenixDependencies.mozilla_support_extensions
implementation Deps.mozilla_support_base implementation FenixDependencies.mozilla_support_base
implementation Deps.mozilla_support_rusterrors implementation FenixDependencies.mozilla_support_rusterrors
implementation Deps.mozilla_support_images implementation FenixDependencies.mozilla_support_images
implementation Deps.mozilla_support_ktx implementation FenixDependencies.mozilla_support_ktx
implementation Deps.mozilla_support_rustlog implementation FenixDependencies.mozilla_support_rustlog
implementation Deps.mozilla_support_utils implementation FenixDependencies.mozilla_support_utils
implementation Deps.mozilla_support_locale implementation FenixDependencies.mozilla_support_locale
implementation Deps.mozilla_ui_colors implementation FenixDependencies.mozilla_ui_colors
implementation Deps.mozilla_ui_icons implementation FenixDependencies.mozilla_ui_icons
implementation Deps.mozilla_lib_publicsuffixlist implementation FenixDependencies.mozilla_lib_publicsuffixlist
implementation Deps.mozilla_ui_widgets implementation FenixDependencies.mozilla_ui_widgets
implementation Deps.mozilla_ui_tabcounter implementation FenixDependencies.mozilla_ui_tabcounter
implementation Deps.mozilla_lib_crash implementation FenixDependencies.mozilla_lib_crash
implementation Deps.lib_crash_sentry implementation FenixDependencies.lib_crash_sentry
implementation Deps.mozilla_lib_state implementation FenixDependencies.mozilla_lib_state
implementation Deps.mozilla_lib_dataprotect implementation FenixDependencies.mozilla_lib_dataprotect
debugImplementation Deps.leakcanary debugImplementation FenixDependencies.leakcanary
forkDebugImplementation Deps.leakcanary
implementation FenixDependencies.androidx_annotation
implementation Deps.androidx_annotation implementation FenixDependencies.androidx_compose_ui
implementation Deps.androidx_compose_ui implementation FenixDependencies.androidx_compose_ui_tooling
implementation Deps.androidx_compose_ui_tooling implementation FenixDependencies.androidx_compose_foundation
implementation Deps.androidx_compose_foundation implementation FenixDependencies.androidx_compose_material
implementation Deps.androidx_compose_material implementation FenixDependencies.androidx_legacy
implementation Deps.androidx_legacy implementation FenixDependencies.androidx_biometric
implementation Deps.androidx_biometric implementation FenixDependencies.androidx_paging
implementation Deps.androidx_paging implementation FenixDependencies.androidx_preference
implementation Deps.androidx_preference implementation FenixDependencies.androidx_fragment
implementation Deps.androidx_fragment implementation FenixDependencies.androidx_navigation_fragment
implementation Deps.androidx_navigation_fragment implementation FenixDependencies.androidx_navigation_ui
implementation Deps.androidx_navigation_ui implementation FenixDependencies.androidx_recyclerview
implementation Deps.androidx_recyclerview implementation FenixDependencies.androidx_lifecycle_common
implementation Deps.androidx_lifecycle_common implementation FenixDependencies.androidx_lifecycle_livedata
implementation Deps.androidx_lifecycle_livedata implementation FenixDependencies.androidx_lifecycle_process
implementation Deps.androidx_lifecycle_process implementation FenixDependencies.androidx_lifecycle_runtime
implementation Deps.androidx_lifecycle_runtime implementation FenixDependencies.androidx_lifecycle_viewmodel
implementation Deps.androidx_lifecycle_viewmodel implementation FenixDependencies.androidx_core
implementation Deps.androidx_core implementation FenixDependencies.androidx_core_ktx
implementation Deps.androidx_core_ktx implementation FenixDependencies.androidx_transition
implementation Deps.androidx_transition implementation FenixDependencies.androidx_work_ktx
implementation Deps.androidx_work_ktx implementation FenixDependencies.androidx_datastore
implementation Deps.androidx_datastore implementation FenixDependencies.protobuf_javalite
implementation Deps.protobuf_javalite implementation FenixDependencies.google_material
implementation Deps.google_material
androidTestImplementation FenixDependencies.uiautomator
androidTestImplementation Deps.uiautomator
androidTestImplementation "tools.fastlane:screengrab:2.0.0" androidTestImplementation "tools.fastlane:screengrab:2.0.0"
// This Falcon version is added to maven central now required for Screengrab // This Falcon version is added to maven central now required for Screengrab
implementation 'com.jraska:falcon:2.2.0' implementation 'com.jraska:falcon:2.2.0'
androidTestImplementation Deps.androidx_compose_ui_test androidTestImplementation FenixDependencies.androidx_compose_ui_test
androidTestImplementation Deps.espresso_core, { androidTestImplementation FenixDependencies.espresso_core, {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
} }
androidTestImplementation(Deps.espresso_contrib) { androidTestImplementation(FenixDependencies.espresso_contrib) {
exclude module: 'appcompat-v7' exclude module: 'appcompat-v7'
exclude module: 'support-v4' exclude module: 'support-v4'
exclude module: 'support-annotations' exclude module: 'support-annotations'
@ -659,36 +659,36 @@ dependencies {
exclude module: 'protobuf-lite' exclude module: 'protobuf-lite'
} }
androidTestImplementation Deps.androidx_test_core androidTestImplementation FenixDependencies.androidx_test_core
androidTestImplementation Deps.espresso_idling_resources androidTestImplementation FenixDependencies.espresso_idling_resources
androidTestImplementation Deps.espresso_intents androidTestImplementation FenixDependencies.espresso_intents
androidTestImplementation Deps.tools_test_runner androidTestImplementation FenixDependencies.tools_test_runner
androidTestImplementation Deps.tools_test_rules androidTestImplementation FenixDependencies.tools_test_rules
androidTestUtil Deps.orchestrator androidTestUtil FenixDependencies.orchestrator
androidTestImplementation Deps.espresso_core, { androidTestImplementation FenixDependencies.espresso_core, {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
} }
androidTestImplementation Deps.androidx_junit androidTestImplementation FenixDependencies.androidx_junit
androidTestImplementation Deps.androidx_test_extensions androidTestImplementation FenixDependencies.androidx_test_extensions
androidTestImplementation Deps.androidx_work_testing androidTestImplementation FenixDependencies.androidx_work_testing
androidTestImplementation Deps.androidx_benchmark_junit4 androidTestImplementation FenixDependencies.androidx_benchmark_junit4
androidTestImplementation Deps.mockwebserver androidTestImplementation FenixDependencies.mockwebserver
testImplementation Deps.mozilla_support_test testImplementation FenixDependencies.mozilla_support_test
testImplementation Deps.mozilla_support_test_libstate testImplementation FenixDependencies.mozilla_support_test_libstate
testImplementation Deps.androidx_junit testImplementation FenixDependencies.androidx_junit
testImplementation Deps.androidx_test_extensions testImplementation FenixDependencies.androidx_test_extensions
testImplementation Deps.androidx_work_testing testImplementation FenixDependencies.androidx_work_testing
testImplementation (Deps.robolectric) { testImplementation (FenixDependencies.robolectric) {
exclude group: 'org.apache.maven' exclude group: 'org.apache.maven'
} }
testImplementation 'org.apache.maven:maven-ant-tasks:2.1.3' testImplementation 'org.apache.maven:maven-ant-tasks:2.1.3'
implementation Deps.mozilla_support_rusthttp implementation FenixDependencies.mozilla_support_rusthttp
androidTestImplementation Deps.mockk_android androidTestImplementation FenixDependencies.mockk_android
testImplementation Deps.mockk testImplementation FenixDependencies.mockk
// For the initial release of Glean 19, we require consumer applications to // For the initial release of Glean 19, we require consumer applications to
// depend on a separate library for unit tests. This will be removed in future releases. // depend on a separate library for unit tests. This will be removed in future releases.
@ -702,9 +702,9 @@ protobuf {
// See https://github.com/mozilla-mobile/fenix/issues/22321 // See https://github.com/mozilla-mobile/fenix/issues/22321
protoc { protoc {
if (osdetector.os == "osx") { if (osdetector.os == "osx") {
artifact = "${Deps.protobuf_compiler}:osx-x86_64" artifact = "${FenixDependencies.protobuf_compiler}:osx-x86_64"
} else { } else {
artifact = Deps.protobuf_compiler artifact = FenixDependencies.protobuf_compiler
} }
} }

File diff suppressed because it is too large Load Diff

@ -13,31 +13,31 @@
<h3>Level 1 (Basic) List</h3> <h3>Level 1 (Basic) List</h3>
<p>social-track-digest256:</p> <p>social-track-digest256:</p>
<img <img
src="https://social-track-digest256.dummytracker.org/test_not_blocked.png" src="https://social-track-digest256.dummytracker.org/test_not_blocked.png" alt="social not blocked"
onerror="this.onerror=null;this.src='https://not-a-tracker.dummytracker.org/test_blocked.png'"> onerror="this.onerror=null;this.src='https://not-a-tracker.dummytracker.org/test_blocked.png';this.alt='social blocked'">
<br/> <br/>
<p>ads-track-digest256:</p> <p>ads-track-digest256:</p>
<img <img
src="https://ads-track-digest256.dummytracker.org/test_not_blocked.png" src="https://ads-track-digest256.dummytracker.org/test_not_blocked.png"
onerror="this.onerror=null;this.src='https://not-a-tracker.dummytracker.org/test_blocked.png'"> onerror="this.onerror=null;this.src='https://not-a-tracker.dummytracker.org/test_blocked.png';this.alt='ads blocked'">
<br/> <br/>
<p>analytics-track-digest256:</p> <p>analytics-track-digest256:</p>
<img <img
src="https://analytics-track-digest256.dummytracker.org/test_not_blocked.png" src="https://analytics-track-digest256.dummytracker.org/test_not_blocked.png"
onerror="this.onerror=null;this.src='https://not-a-tracker.dummytracker.org/test_blocked.png'"> onerror="this.onerror=null;this.src='https://not-a-tracker.dummytracker.org/test_blocked.png';this.alt='analytics blocked'">
<br/> <br/>
<p>Fingerprinting: <p>Fingerprinting:
<pre id="result">test not run</pre> <pre id="result">test not run</pre>
<script src="https://base-fingerprinting-track-digest256.dummytracker.org/tracker.js" <script src="https://base-fingerprinting-track-digest256.dummytracker.org/tracker.js"
onerror="this.onerror=null;var result=document.getElementById('result');result.innerHTML='blocked';" onerror="this.onerror=null;var result=document.getElementById('result');result.innerHTML='Fingerprinting blocked';"
onload="this.onload=null;var result=document.getElementById('result');result.innerHTML='NOT blocked';" onload="this.onload=null;var result=document.getElementById('result');result.innerHTML='Fingerprinting NOT blocked';"
></script> ></script>
</p> </p>
<br/> <br/>
<p>Cryptomining: <p>Cryptomining:
<img <img
src="https://base-cryptomining-track-digest256.dummytracker.org/test_not_blocked.png" alt="not blocked" src="https://base-cryptomining-track-digest256.dummytracker.org/test_not_blocked.png" alt="Cryptomining not blocked"
onerror="this.onerror=null;this.src='https://not-a-tracker.dummytracker.org/test_blocked.png';this.alt='blocked'"> onerror="this.onerror=null;this.src='https://not-a-tracker.dummytracker.org/test_blocked.png';this.alt='Cryptomining blocked'">
</p> </p>
<p><b>Cookie blocking</b> <p><b>Cookie blocking</b>

@ -28,6 +28,7 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -143,6 +144,7 @@ class BaselinePingTest {
return null return null
} }
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807288")
@Test @Test
fun validateBaselinePing() { fun validateBaselinePing() {
// Wait for the app to be idle/ready. // Wait for the app to be idle/ready.

@ -4,6 +4,8 @@
package org.mozilla.fenix.helpers package org.mozilla.fenix.helpers
import org.mozilla.fenix.helpers.TestHelper.getSponsoredShortcutTitle
object Constants { object Constants {
// Device or AVD requires a Google Services Android OS installation // Device or AVD requires a Google Services Android OS installation
@ -28,4 +30,18 @@ object Constants {
"Bing" to "firefox&pc=MOZB&form=MOZMBA", "Bing" to "firefox&pc=MOZB&form=MOZMBA",
"DuckDuckGo" to "t=fpas", "DuckDuckGo" to "t=fpas",
) )
val firstSponsoredShortcutTitle by lazy { getSponsoredShortcutTitle(2) }
val secondSponsoredShortcutTitle by lazy { getSponsoredShortcutTitle(3) }
// Expected for en-us defaults
val defaultTopSitesList by lazy {
mapOf(
"Google" to "Google",
"First sponsored shortcut" to firstSponsoredShortcutTitle,
"Second sponsored shortcut" to secondSponsoredShortcutTitle,
"Top Articles" to "Top Articles",
"Wikipedia" to "Wikipedia",
)
}
} }

@ -0,0 +1,85 @@
/* 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.helpers
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiSelector
import org.junit.Assert.assertTrue
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.mDevice
/**
* Helper for querying and interacting with items based on their matchers.
*/
object MatcherHelper {
fun itemWithResId(resourceId: String) =
mDevice.findObject(UiSelector().resourceId(resourceId))
fun itemContainingText(itemText: String) =
mDevice.findObject(UiSelector().textContains(itemText))
fun itemWithDescription(description: String) =
mDevice.findObject(UiSelector().descriptionContains(description))
fun checkedItemWithResId(resourceId: String, isChecked: Boolean) =
mDevice.findObject(UiSelector().resourceId(resourceId).checked(isChecked))
fun checkedItemWithResIdAndText(resourceId: String, text: String, isChecked: Boolean) =
mDevice.findObject(
UiSelector()
.resourceId(resourceId)
.textContains(text)
.checked(isChecked),
)
fun itemWithResIdAndDescription(resourceId: String, description: String) =
mDevice.findObject(UiSelector().resourceId(resourceId).descriptionContains(description))
fun itemWithResIdAndText(resourceId: String, text: String) =
mDevice.findObject(UiSelector().resourceId(resourceId).text(text))
fun assertItemWithResIdExists(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
fun assertItemContainingTextExists(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
fun assertItemWithDescriptionExists(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
fun assertCheckedItemWithResIdExists(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
fun assertCheckedItemWithResIdAndTextExists(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
fun assertItemWithResIdAndDescriptionExists(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
fun assertItemWithResIdAndTextExists(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
}

@ -445,4 +445,25 @@ object TestHelper {
clipBoard.setPrimaryClip(clipData) clipBoard.setPrimaryClip(clipData)
} }
/**
* Returns sponsored shortcut title based on the index.
*/
fun getSponsoredShortcutTitle(position: Int): String {
val sponsoredShortcut = mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/top_site_item")
.index(position - 1),
).getChild(
UiSelector()
.resourceId("$packageName:id/top_site_title"),
).text
return sponsoredShortcut
}
fun verifyLightThemeApplied(expected: Boolean) =
assertFalse("Light theme not selected", expected)
fun verifyDarkThemeApplied(expected: Boolean) = assertTrue("Dark theme not selected", expected)
} }

@ -17,7 +17,6 @@ import androidx.test.uiautomator.Until
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -164,7 +163,6 @@ class MenuScreenShotTest : ScreenshotTest() {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun tabMenuTest() { fun tabMenuTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar { navigationToolbar {

@ -36,16 +36,18 @@ class CollectionTest {
private val collectionName = "First Collection" private val collectionName = "First Collection"
@get:Rule @get:Rule
val composeTestRule = AndroidComposeTestRule( val composeTestRule =
// disabling these features to have better visibility of Collections, AndroidComposeTestRule(
// and to avoid multiple matches on tab items HomeActivityIntentTestRule(
HomeActivityIntentTestRule( isHomeOnboardingDialogEnabled = false,
isPocketEnabled = false, isJumpBackInCFREnabled = false,
isJumpBackInCFREnabled = false, isRecentTabsFeatureEnabled = false,
isRecentTabsFeatureEnabled = false, isRecentlyVisitedFeatureEnabled = false,
isRecentlyVisitedFeatureEnabled = false, isPocketEnabled = false,
), isWallpaperOnboardingEnabled = false,
) { it.activity } isTCPCFREnabled = false,
),
) { it.activity }
@Before @Before
fun setUp() { fun setUp() {
@ -63,7 +65,6 @@ class CollectionTest {
@SmokeTest @SmokeTest
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun createFirstCollectionTest() { fun createFirstCollectionTest() {
val firstWebPage = getGenericAsset(mockWebServer, 1) val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2) val secondWebPage = getGenericAsset(mockWebServer, 2)
@ -76,7 +77,6 @@ class CollectionTest {
}.submitQuery(secondWebPage.url.toString()) { }.submitQuery(secondWebPage.url.toString()) {
mDevice.waitForIdle() mDevice.waitForIdle()
}.goToHomescreen { }.goToHomescreen {
swipeToBottom()
}.clickSaveTabsToCollectionButton { }.clickSaveTabsToCollectionButton {
longClickTab(firstWebPage.title) longClickTab(firstWebPage.title)
selectTab(secondWebPage.title, numOfTabs = 2) selectTab(secondWebPage.title, numOfTabs = 2)
@ -96,7 +96,6 @@ class CollectionTest {
@SmokeTest @SmokeTest
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun verifyExpandedCollectionItemsTest() { fun verifyExpandedCollectionItemsTest() {
val webPage = getGenericAsset(mockWebServer, 1) val webPage = getGenericAsset(mockWebServer, 1)
val webPageUrl = webPage.url.host.toString() val webPageUrl = webPage.url.host.toString()
@ -146,7 +145,6 @@ class CollectionTest {
@SmokeTest @SmokeTest
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun openAllTabsInCollectionTest() { fun openAllTabsInCollectionTest() {
val firstTestPage = getGenericAsset(mockWebServer, 1) val firstTestPage = getGenericAsset(mockWebServer, 1)
val secondTestPage = getGenericAsset(mockWebServer, 2) val secondTestPage = getGenericAsset(mockWebServer, 2)
@ -179,7 +177,6 @@ class CollectionTest {
@SmokeTest @SmokeTest
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun shareCollectionTest() { fun shareCollectionTest() {
val firstWebsite = getGenericAsset(mockWebServer, 1) val firstWebsite = getGenericAsset(mockWebServer, 1)
val secondWebsite = getGenericAsset(mockWebServer, 2) val secondWebsite = getGenericAsset(mockWebServer, 2)
@ -204,11 +201,10 @@ class CollectionTest {
} }
} }
@SmokeTest
@Test
// Test running on beta/release builds in CI: // Test running on beta/release builds in CI:
// caution when making changes to it, so they don't block the builds // caution when making changes to it, so they don't block the builds
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087") @SmokeTest
@Test
fun deleteCollectionTest() { fun deleteCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1) val webPage = getGenericAsset(mockWebServer, 1)
@ -231,9 +227,8 @@ class CollectionTest {
} }
} }
@Test
// open a webpage, and add currently opened tab to existing collection // open a webpage, and add currently opened tab to existing collection
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087") @Test
fun mainMenuSaveToExistingCollection() { fun mainMenuSaveToExistingCollection() {
val firstWebPage = getGenericAsset(mockWebServer, 1) val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2) val secondWebPage = getGenericAsset(mockWebServer, 2)
@ -260,7 +255,6 @@ class CollectionTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun verifyAddTabButtonOfCollectionMenu() { fun verifyAddTabButtonOfCollectionMenu() {
val firstWebPage = getGenericAsset(mockWebServer, 1) val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2) val secondWebPage = getGenericAsset(mockWebServer, 2)
@ -287,7 +281,6 @@ class CollectionTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun renameCollectionTest() { fun renameCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1) val webPage = getGenericAsset(mockWebServer, 1)
@ -308,7 +301,6 @@ class CollectionTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun createSecondCollectionTest() { fun createSecondCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1) val webPage = getGenericAsset(mockWebServer, 1)
@ -331,7 +323,6 @@ class CollectionTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun removeTabFromCollectionTest() { fun removeTabFromCollectionTest() {
val webPage = getGenericAsset(mockWebServer, 1) val webPage = getGenericAsset(mockWebServer, 1)
@ -354,7 +345,6 @@ class CollectionTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun swipeLeftToRemoveTabFromCollectionTest() { fun swipeLeftToRemoveTabFromCollectionTest() {
val testPage = getGenericAsset(mockWebServer, 1) val testPage = getGenericAsset(mockWebServer, 1)
@ -381,7 +371,6 @@ class CollectionTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun swipeRightToRemoveTabFromCollectionTest() { fun swipeRightToRemoveTabFromCollectionTest() {
val testPage = getGenericAsset(mockWebServer, 1) val testPage = getGenericAsset(mockWebServer, 1)
@ -493,7 +482,7 @@ class CollectionTest {
homeScreen { homeScreen {
verifySnackBarText("Collection deleted") verifySnackBarText("Collection deleted")
clickUndoCollectionDeletion("UNDO") clickUndoSnackBarButton()
verifyCollectionIsDisplayed(collectionName, true) verifyCollectionIsDisplayed(collectionName, true)
} }
} }

@ -33,7 +33,7 @@ class DownloadFileTypesTest(fileName: String) {
@JvmStatic @JvmStatic
@Parameterized.Parameters @Parameterized.Parameters
fun downloadList() = listOf( fun downloadList() = listOf(
"washington.pdf", "smallZip.zip",
"MyDocument.docx", "MyDocument.docx",
"audioSample.mp3", "audioSample.mp3",
"textfile.txt", "textfile.txt",

@ -8,7 +8,6 @@ import androidx.core.net.toUri
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.customannotations.SmokeTest
@ -165,7 +164,6 @@ class EnhancedTrackingProtectionTest {
} }
} }
@Ignore("Permanent failure: https://github.com/mozilla-mobile/fenix/issues/27312")
@Test @Test
fun testStrictVisitSheetDetails() { fun testStrictVisitSheetDetails() {
appContext.settings().setStrictETP() appContext.settings().setStrictETP()
@ -182,7 +180,11 @@ class EnhancedTrackingProtectionTest {
navigationToolbar { navigationToolbar {
}.enterURLAndEnterToBrowser(trackingProtectionTest.url) { }.enterURLAndEnterToBrowser(trackingProtectionTest.url) {
verifyTrackingProtectionWebContent("blocked") verifyTrackingProtectionWebContent("social blocked")
verifyTrackingProtectionWebContent("ads blocked")
verifyTrackingProtectionWebContent("analytics blocked")
verifyTrackingProtectionWebContent("Fingerprinting blocked")
verifyTrackingProtectionWebContent("Cryptomining blocked")
} }
enhancedTrackingProtection { enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet { }.openEnhancedTrackingProtectionSheet {
@ -197,7 +199,6 @@ class EnhancedTrackingProtectionTest {
} }
} }
@Ignore("Permanent failure: https://github.com/mozilla-mobile/fenix/issues/27312")
@SmokeTest @SmokeTest
@Test @Test
fun customTrackingProtectionSettingsTest() { fun customTrackingProtectionSettingsTest() {
@ -217,7 +218,13 @@ class EnhancedTrackingProtectionTest {
// browsing a basic page to allow GV to load on a fresh run // browsing a basic page to allow GV to load on a fresh run
}.enterURLAndEnterToBrowser(genericWebPage.url) { }.enterURLAndEnterToBrowser(genericWebPage.url) {
}.openNavigationToolbar { }.openNavigationToolbar {
}.enterURLAndEnterToBrowser(trackingPage.url) {} }.enterURLAndEnterToBrowser(trackingPage.url) {
verifyTrackingProtectionWebContent("social blocked")
verifyTrackingProtectionWebContent("ads blocked")
verifyTrackingProtectionWebContent("analytics blocked")
verifyTrackingProtectionWebContent("Fingerprinting blocked")
verifyTrackingProtectionWebContent("Cryptomining blocked")
}
enhancedTrackingProtection { enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet { }.openEnhancedTrackingProtectionSheet {

@ -155,81 +155,6 @@ class HomeScreenTest {
} }
} }
@Test
fun dismissOnboardingUsingSettingsTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openSettings {
verifyGeneralHeading()
}.goBack {
verifyExistingTopSitesList()
}
}
@Test
fun dismissOnboardingUsingBookmarksTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openBookmarks {
verifyBookmarksMenuView()
navigateUp()
}
homeScreen {
verifyExistingTopSitesList()
}
}
@Test
fun dismissOnboardingUsingHelpTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isJumpBackInCFREnabled = false
it.isWallpaperOnboardingEnabled = false
}
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openHelp {
verifyHelpUrl()
}.goBack {
verifyExistingTopSitesList()
}
}
@Test
fun dismissOnboardingWithPageLoadTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isJumpBackInCFREnabled = false
it.isWallpaperOnboardingEnabled = false
}
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
verifyWelcomeHeader()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.goToHomescreen {
verifyExistingTopSitesList()
}
}
@Test
fun toolbarTapDoesntDismissOnboardingTest() {
homeScreen {
verifyWelcomeHeader()
}.openSearch {
verifyScanButton()
verifySearchEngineButton()
verifyKeyboardVisibility()
}.dismissSearchBar {
verifyWelcomeHeader()
}
}
@Test @Test
fun verifyPocketHomepageStoriesTest() { fun verifyPocketHomepageStoriesTest() {
activityTestRule.activityRule.applySettingsExceptions { activityTestRule.activityRule.applySettingsExceptions {

@ -65,7 +65,7 @@ class NavigationToolbarTest {
}.enterURLAndEnterToBrowser(nextWebPage.url) { }.enterURLAndEnterToBrowser(nextWebPage.url) {
verifyUrl(nextWebPage.url.toString()) verifyUrl(nextWebPage.url.toString())
}.openThreeDotMenu { }.openThreeDotMenu {
}.goBack { }.goToPreviousPage {
mDevice.waitForIdle() mDevice.waitForIdle()
verifyUrl(defaultWebPage.url.toString()) verifyUrl(defaultWebPage.url.toString())
} }
@ -84,7 +84,7 @@ class NavigationToolbarTest {
mDevice.waitForIdle() mDevice.waitForIdle()
verifyUrl(nextWebPage.url.toString()) verifyUrl(nextWebPage.url.toString())
}.openThreeDotMenu { }.openThreeDotMenu {
}.goBack { }.goToPreviousPage {
mDevice.waitForIdle() mDevice.waitForIdle()
verifyUrl(defaultWebPage.url.toString()) verifyUrl(defaultWebPage.url.toString())
} }

@ -0,0 +1,223 @@
package org.mozilla.fenix.ui
import android.content.res.Configuration
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.verifyDarkThemeApplied
import org.mozilla.fenix.helpers.TestHelper.verifyLightThemeApplied
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
class OnboardingTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val privacyNoticeLink = "mozilla.org/en-US/privacy/firefox"
@get:Rule
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
private fun getUITheme(): Boolean {
val mode =
activityTestRule.activity.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK)
return when (mode) {
Configuration.UI_MODE_NIGHT_YES -> true // dark theme is set
Configuration.UI_MODE_NIGHT_NO -> false // dark theme is not set, using light theme
else -> false // default option is light theme
}
}
// Verifies the first run onboarding screen
@SmokeTest
@Test
fun firstRunScreenTest() {
homeScreen {
verifyHomeScreenAppBarItems()
verifyHomeScreenWelcomeItems()
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = false,
isAutomaticThemeChecked = true,
)
verifyToolbarPlacementCard(isBottomChecked = true, isTopChecked = false)
verifySignInToSyncCard()
verifyPrivacyProtectionCard(isStandardChecked = true, isStrictChecked = false)
verifyPrivacyNoticeCard()
verifyStartBrowsingSection()
verifyNavigationToolbarItems("0")
}
}
// Verifies the functionality of the onboarding Start Browsing button
@SmokeTest
@Test
fun startBrowsingButtonTest() {
homeScreen {
verifyStartBrowsingButton()
}.clickStartBrowsingButton {
verifySearchView()
}
}
@Test
fun dismissOnboardingUsingSettingsTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openSettings {
verifyGeneralHeading()
}.goBack {
verifyExistingTopSitesList()
}
}
@Test
fun dismissOnboardingUsingBookmarksTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openBookmarks {
verifyBookmarksMenuView()
navigateUp()
}
homeScreen {
verifyExistingTopSitesList()
}
}
@Test
fun dismissOnboardingUsingHelpTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openHelp {
verifyHelpUrl()
}.goBack {
verifyExistingTopSitesList()
}
}
@Test
fun toolbarTapDoesntDismissOnboardingTest() {
homeScreen {
verifyWelcomeHeader()
}.openSearch {
verifyScanButton()
verifySearchEngineButton()
verifyKeyboardVisibility()
}.dismissSearchBar {
verifyWelcomeHeader()
}
}
@Test
fun dismissOnboardingWithPageLoadTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
verifyWelcomeHeader()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.goToHomescreen {
verifyExistingTopSitesList()
}
}
@Test
fun chooseYourThemeCardTest() {
homeScreen {
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = false,
isAutomaticThemeChecked = true,
)
clickLightThemeButton()
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = true,
isAutomaticThemeChecked = false,
)
verifyLightThemeApplied(getUITheme())
clickDarkThemeButton()
verifyChooseYourThemeCard(
isDarkThemeChecked = true,
isLightThemeChecked = false,
isAutomaticThemeChecked = false,
)
verifyDarkThemeApplied(getUITheme())
clickAutomaticThemeButton()
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = false,
isAutomaticThemeChecked = true,
)
verifyLightThemeApplied(getUITheme())
}
}
@Test
fun pickYourToolbarPlacementCardTest() {
homeScreen {
verifyToolbarPlacementCard(isBottomChecked = true, isTopChecked = false)
clickTopToolbarPlacementButton()
verifyToolbarPosition(defaultPosition = false)
clickBottomToolbarPlacementButton()
verifyToolbarPosition(defaultPosition = true)
}
}
@Test
fun privacyProtectionByDefaultCardTest() {
homeScreen {
verifyPrivacyProtectionCard(isStandardChecked = true, isStrictChecked = false)
clickStrictTrackingProtectionButton()
verifyPrivacyProtectionCard(isStandardChecked = false, isStrictChecked = true)
clickStandardTrackingProtectionButton()
verifyPrivacyProtectionCard(isStandardChecked = true, isStrictChecked = false)
}
}
@Test
fun pickUpWhereYouLeftOffCardTest() {
homeScreen {
verifySignInToSyncCard()
}.clickSignInButton {
verifyTurnOnSyncMenu()
}
}
@Test
fun youControlYourDataCardTest() {
homeScreen {
verifyPrivacyNoticeCard()
}.clickPrivacyNoticeButton {
verifyUrl(privacyNoticeLink)
}.goBack {
verifyPrivacyNoticeCard()
}
}
}

@ -1,6 +1,7 @@
package org.mozilla.fenix.ui package org.mozilla.fenix.ui
import androidx.core.net.toUri import androidx.core.net.toUri
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.customannotations.SmokeTest
@ -45,6 +46,7 @@ class PwaTest {
} }
} }
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/28212")
@SmokeTest @SmokeTest
@Test @Test
fun emailLinkPWATest() { fun emailLinkPWATest() {

@ -141,7 +141,7 @@ class ReaderViewTest {
verifyReaderViewDetected(true) verifyReaderViewDetected(true)
}.openThreeDotMenu { }.openThreeDotMenu {
verifyReaderViewAppearance(false) verifyReaderViewAppearance(false)
}.close { } }.closeBrowserMenuToBrowser { }
} }
@Test @Test

@ -10,6 +10,8 @@ import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.verifyDarkThemeApplied
import org.mozilla.fenix.helpers.TestHelper.verifyLightThemeApplied
import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar import org.mozilla.fenix.ui.robots.navigationToolbar

@ -17,6 +17,7 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper import org.mozilla.fenix.helpers.TestAssetHelper
@ -53,6 +54,7 @@ class SettingsPrivacyTest {
@Before @Before
fun setUp() { fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
appContext.settings().userOptOutOfReEngageCookieBannerDialog = true
mockWebServer = MockWebServer().apply { mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher() dispatcher = AndroidAssetDispatcher()
start() start()

@ -98,53 +98,81 @@ class SettingsSearchTest {
@Test @Test
fun toggleSearchBookmarksAndHistoryTest() { fun toggleSearchBookmarksAndHistoryTest() {
// Bookmarks 2 websites, toggles the bookmarks and history search settings off,
// then verifies if the websites do not show in the suggestions.
val page1 = getGenericAsset(mockWebServer, 1) val page1 = getGenericAsset(mockWebServer, 1)
val page2 = getGenericAsset(mockWebServer, 2) val page2 = getGenericAsset(mockWebServer, 2)
navigationToolbar { navigationToolbar {
}.enterURLAndEnterToBrowser(page1.url) { }.enterURLAndEnterToBrowser(page1.url) {
}.openTabDrawer {
closeTab()
}
homeScreen {
}.openSearch {
typeSearch("test")
verifyFirefoxSuggestResults(
activityTestRule,
"test",
"Firefox Suggest",
"Test_Page_1",
)
}.clickSearchSuggestion("Test_Page_1") {
verifyUrl(page1.url.toString()) verifyUrl(page1.url.toString())
}.openThreeDotMenu {
}.bookmarkPage {
}.openTabDrawer { }.openTabDrawer {
closeTab() closeTab()
} }
navigationToolbar { navigationToolbar {
}.enterURLAndEnterToBrowser(page2.url) { }.enterURLAndEnterToBrowser(page2.url) {
verifyUrl(page2.url.toString())
}.openThreeDotMenu { }.openThreeDotMenu {
}.bookmarkPage { }.bookmarkPage {
}.openTabDrawer { }.openTabDrawer {
closeTab() closeTab()
} }
// Verifies that bookmarks & history suggestions are shown
homeScreen {
}.openThreeDotMenu {
}.openHistory {
verifyHistoryListExists()
clickDeleteHistoryButton("Test_Page_2")
}
exitMenu()
homeScreen { homeScreen {
}.openSearch { }.openSearch {
typeSearch("test") typeSearch("test")
expandSearchSuggestionsList() verifyFirefoxSuggestResults(
verifyFirefoxSuggestResults(activityTestRule, "Firefox Suggest") activityTestRule,
verifyFirefoxSuggestResults(activityTestRule, "Test_Page_1") "test",
verifyFirefoxSuggestResults(activityTestRule, "Test_Page_2") "Firefox Suggest",
}.dismissSearchBar { "Test_Page_2",
)
}.clickSearchSuggestion("Test_Page_2") {
verifyUrl(page2.url.toString())
}.openTabDrawer {
closeTab()
}
homeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openSettings { }.openSettings {
}.openSearchSubMenu { }.openSearchSubMenu {
// Disables the search bookmarks & history settings
verifySearchBookmarks()
switchSearchBookmarksToggle()
switchSearchHistoryToggle() switchSearchHistoryToggle()
exitMenu() switchSearchBookmarksToggle()
} }
// Verifies that bookmarks and history suggestions are not shown
exitMenu()
homeScreen { homeScreen {
}.openSearch { }.openSearch {
typeSearch("test") typeSearch("test")
expandSearchSuggestionsList() verifyNoSuggestionsAreDisplayed(
verifyNoSuggestionsAreDisplayed(activityTestRule, "Firefox Suggest") activityTestRule,
verifyNoSuggestionsAreDisplayed(activityTestRule, "Test_Page_1") "Firefox Suggest",
verifyNoSuggestionsAreDisplayed(activityTestRule, "Test_Page_2") "Test_Page_1",
"Test_Page_2",
)
} }
} }
@ -453,6 +481,7 @@ class SettingsSearchTest {
"eBay", "eBay",
"Wikipedia", "Wikipedia",
) )
scrollToSearchEngineSettings(activityTestRule)
}.clickSearchEngineSettings(activityTestRule) { }.clickSearchEngineSettings(activityTestRule) {
toggleShowSearchShortcuts() toggleShowSearchShortcuts()
verifyShowSearchEnginesToggleState(false) verifyShowSearchEnginesToggleState(false)

@ -17,7 +17,6 @@ import mozilla.components.concept.engine.mediasession.MediaSession
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.IntentReceiverActivity
@ -106,36 +105,6 @@ class SmokeTest {
featureSettingsHelper.resetAllFeatureFlags() featureSettingsHelper.resetAllFeatureFlags()
} }
// Verifies the first run onboarding screen
@Test
fun firstRunScreenTest() {
homeScreen {
verifyHomeScreenAppBarItems()
verifyHomeScreenWelcomeItems()
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = false,
isAutomaticThemeChecked = true,
)
verifyToolbarPlacementCard(isBottomChecked = true, isTopChecked = false)
verifySignInToSyncCard()
verifyPrivacyProtectionCard(isStandardChecked = true, isStrictChecked = false)
verifyPrivacyNoticeCard()
verifyStartBrowsingSection()
verifyNavigationToolbarItems("0")
}
}
// Verifies the functionality of the onboarding Start Browsing button
@Test
fun startBrowsingButtonTest() {
homeScreen {
verifyStartBrowsingButton()
}.clickStartBrowsingButton {
verifySearchView()
}
}
/* Verifies the nav bar: /* Verifies the nav bar:
- opening a web page - opening a web page
- the existence of nav bar items - the existence of nav bar items
@ -172,7 +141,7 @@ class SmokeTest {
}.enterURLAndEnterToBrowser(defaultWebPage.url) { }.enterURLAndEnterToBrowser(defaultWebPage.url) {
waitForPageToLoad() waitForPageToLoad()
}.openThreeDotMenu { }.openThreeDotMenu {
verifyPageThreeDotMainMenuItems() verifyPageThreeDotMainMenuItems(isRequestDesktopSiteEnabled = false)
} }
} }
@ -283,7 +252,7 @@ class SmokeTest {
}.openThreeDotMenu { }.openThreeDotMenu {
expandMenu() expandMenu()
}.openAddToHomeScreen { }.openAddToHomeScreen {
verifyShortcutNameField("Test_Page_1") verifyShortcutTextFieldTitle("Test_Page_1")
addShortcutName(shortcutTitle) addShortcutName(shortcutTitle)
clickAddShortcutButton() clickAddShortcutButton()
clickAddAutomaticallyButton() clickAddAutomaticallyButton()
@ -524,7 +493,6 @@ class SmokeTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun shareTabsFromTabsTrayTest() { fun shareTabsFromTabsTrayTest() {
val firstWebsite = TestAssetHelper.getGenericAsset(mockWebServer, 1) val firstWebsite = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebsite = TestAssetHelper.getGenericAsset(mockWebServer, 2) val secondWebsite = TestAssetHelper.getGenericAsset(mockWebServer, 2)
@ -572,7 +540,6 @@ class SmokeTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun privateTabsTrayWithOpenedTabTest() { fun privateTabsTrayWithOpenedTabTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1) val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -582,7 +549,6 @@ class SmokeTest {
homeScreen { homeScreen {
}.openNavigationToolbar { }.openNavigationToolbar {
}.enterURLAndEnterToBrowser(website.url) { }.enterURLAndEnterToBrowser(website.url) {
mDevice.waitForIdle()
}.openTabDrawer { }.openTabDrawer {
verifyNormalBrowsingButtonIsSelected(false) verifyNormalBrowsingButtonIsSelected(false)
verifyPrivateBrowsingButtonIsSelected(true) verifyPrivateBrowsingButtonIsSelected(true)
@ -592,7 +558,8 @@ class SmokeTest {
verifyExistingTabList() verifyExistingTabList()
verifyExistingOpenTabs(website.title) verifyExistingOpenTabs(website.title)
verifyCloseTabsButton(website.title) verifyCloseTabsButton(website.title)
verifyOpenedTabThumbnail() // Disabled step due to ongoing tabs tray compose refactoring, see: https://github.com/mozilla-mobile/fenix/issues/21318
// verifyOpenedTabThumbnail()
verifyPrivateBrowsingNewTabButton() verifyPrivateBrowsingNewTabButton()
} }
} }
@ -772,7 +739,6 @@ class SmokeTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun tabMediaControlButtonTest() { fun tabMediaControlButtonTest() {
val audioTestPage = TestAssetHelper.getAudioPageAsset(mockWebServer) val audioTestPage = TestAssetHelper.getAudioPageAsset(mockWebServer)

@ -0,0 +1,252 @@
/* 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.ui
import androidx.test.platform.app.InstrumentationRegistry
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.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.Constants.defaultTopSitesList
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.getSponsoredShortcutTitle
import org.mozilla.fenix.ui.robots.homeScreen
/**
* Tests Sponsored shortcuts functionality
*/
class SponsoredShortcutsTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val defaultSearchEngine = "Amazon.com"
private lateinit var sponsoredShortcutTitle: String
private lateinit var sponsoredShortcutTitle2: String
@get:Rule
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Before
fun setUp() {
TestHelper.appContext.settings().userOptOutOfReEngageCookieBannerDialog = true
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
// Expected for en-us defaults
@SmokeTest
@Test
fun verifySponsoredShortcutsListTest() {
homeScreen {
defaultTopSitesList.values.forEach { value ->
verifyExistingTopSitesTabs(value)
}
}.openThreeDotMenu {
}.openCustomizeHome {
verifySponsoredShortcutsCheckBox(true)
clickSponsoredShortcuts()
verifySponsoredShortcutsCheckBox(false)
}.goBack {
verifyNotExistingSponsoredTopSitesList()
}
}
@Test
fun openSponsoredShortcutTest() {
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
}.openSponsoredShortcut(sponsoredShortcutTitle) {
verifyUrl(sponsoredShortcutTitle)
}
}
@Test
fun openSponsoredShortcutInPrivateBrowsingTest() {
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
}.openContextMenuOnSponsoredShortcut(sponsoredShortcutTitle) {
}.openTopSiteInPrivateTab {
verifyUrl(sponsoredShortcutTitle)
}
}
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/25926")
@Test
fun verifySponsorsAndPrivacyLinkTest() {
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
}.openContextMenuOnSponsoredShortcut(sponsoredShortcutTitle) {
}.clickSponsorsAndPrivacyButton {
verifyUrl("support.mozilla.org/en-US/kb/sponsor-privacy")
}
}
@Test
fun verifySponsoredShortcutsSettingsOptionTest() {
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
}.openContextMenuOnSponsoredShortcut(sponsoredShortcutTitle) {
}.clickSponsoredShortcutsSettingsButton {
verifyHomePageView()
}
}
@Test
fun verifySponsoredShortcutsDetailsTest() {
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
sponsoredShortcutTitle2 = getSponsoredShortcutTitle(3)
verifySponsoredShortcutDetails(sponsoredShortcutTitle, 2)
verifySponsoredShortcutDetails(sponsoredShortcutTitle2, 3)
}
}
// The default search engine should not be displayed as a sponsored shortcut
@Test
fun defaultSearchEngineIsNotDisplayedAsSponsoredShortcutTest() {
val sponsoredShortcutTitle = "Amazon"
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSearchSubMenu {
changeDefaultSearchEngine(defaultSearchEngine)
}
exitMenu()
homeScreen {
verifySponsoredShortcutDoesNotExist(sponsoredShortcutTitle, 2)
verifySponsoredShortcutDoesNotExist(sponsoredShortcutTitle, 3)
}
}
// 1 sponsored shortcut should be displayed if there are 7 pinned top sites
@Test
fun verifySponsoredShortcutsListWithSevenPinnedSitesTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
val thirdWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 3)
val fourthWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 4)
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
sponsoredShortcutTitle2 = getSponsoredShortcutTitle(3)
verifySponsoredShortcutDetails(sponsoredShortcutTitle, 2)
verifySponsoredShortcutDetails(sponsoredShortcutTitle2, 3)
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
verifyPageContent(firstWebPage.content)
}.openThreeDotMenu {
expandMenu()
}.addToFirefoxHome {
}.goToHomescreen {
verifyExistingTopSitesTabs(firstWebPage.title)
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondWebPage.url) {
verifyPageContent(secondWebPage.content)
}.openThreeDotMenu {
expandMenu()
}.addToFirefoxHome {
}.goToHomescreen {
verifyExistingTopSitesTabs(secondWebPage.title)
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(thirdWebPage.url) {
verifyPageContent(thirdWebPage.content)
}.openThreeDotMenu {
expandMenu()
}.addToFirefoxHome {
}.goToHomescreen {
verifyExistingTopSitesTabs(thirdWebPage.title)
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(fourthWebPage.url) {
verifyPageContent(fourthWebPage.content)
}.openThreeDotMenu {
expandMenu()
}.addToFirefoxHome {
}.goToHomescreen {
verifySponsoredShortcutDetails(sponsoredShortcutTitle, 2)
verifySponsoredShortcutDoesNotExist(sponsoredShortcutTitle2, 3)
}
}
// No sponsored shortcuts should be displayed if there are 8 pinned top sites
@Test
fun verifySponsoredShortcutsListWithEightPinnedSitesTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
val thirdWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 3)
val fourthWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 4)
val fifthWebPage = TestAssetHelper.getLoremIpsumAsset(mockWebServer)
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
sponsoredShortcutTitle2 = getSponsoredShortcutTitle(3)
verifySponsoredShortcutDetails(sponsoredShortcutTitle, 2)
verifySponsoredShortcutDetails(sponsoredShortcutTitle2, 3)
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
verifyPageContent(firstWebPage.content)
}.openThreeDotMenu {
expandMenu()
}.addToFirefoxHome {
}.goToHomescreen {
verifyExistingTopSitesTabs(firstWebPage.title)
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(secondWebPage.url) {
verifyPageContent(secondWebPage.content)
}.openThreeDotMenu {
expandMenu()
}.addToFirefoxHome {
}.goToHomescreen {
verifyExistingTopSitesTabs(secondWebPage.title)
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(thirdWebPage.url) {
verifyPageContent(thirdWebPage.content)
}.openThreeDotMenu {
expandMenu()
}.addToFirefoxHome {
}.goToHomescreen {
verifyExistingTopSitesTabs(thirdWebPage.title)
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(fourthWebPage.url) {
verifyPageContent(fourthWebPage.content)
}.openThreeDotMenu {
expandMenu()
}.addToFirefoxHome {
}.goToHomescreen {
verifyExistingTopSitesTabs(fourthWebPage.title)
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(fifthWebPage.url) {
verifyPageContent(fifthWebPage.content)
}.openThreeDotMenu {
expandMenu()
}.addToFirefoxHome {
}.goToHomescreen {
verifySponsoredShortcutDoesNotExist(sponsoredShortcutTitle, 2)
verifySponsoredShortcutDoesNotExist(sponsoredShortcutTitle2, 3)
}
}
}

@ -234,7 +234,6 @@ class TabbedBrowsingTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun verifyPrivateTabUndoSnackBarTest() { fun verifyPrivateTabUndoSnackBarTest() {
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1) val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -314,7 +313,6 @@ class TabbedBrowsingTest {
} }
@Test @Test
@Ignore("Failing after compose migration. See: https://github.com/mozilla-mobile/fenix/issues/26087")
fun verifyOpenTabDetails() { fun verifyOpenTabDetails() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1) val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -328,7 +326,8 @@ class TabbedBrowsingTest {
verifyTabsTrayCounter() verifyTabsTrayCounter()
verifyExistingTabList() verifyExistingTabList()
verifyNormalBrowsingNewTabButton() verifyNormalBrowsingNewTabButton()
verifyOpenedTabThumbnail() // Disabled step due to ongoing tabs tray compose refactoring, see: https://github.com/mozilla-mobile/fenix/issues/21318
// verifyOpenedTabThumbnail()
verifyExistingOpenTabs(defaultWebPage.title) verifyExistingOpenTabs(defaultWebPage.title)
verifyCloseTabsButton(defaultWebPage.title) verifyCloseTabsButton(defaultWebPage.title)
}.openTab(defaultWebPage.title) { }.openTab(defaultWebPage.title) {

@ -11,6 +11,7 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.ui.robots.homeScreen import org.mozilla.fenix.ui.robots.homeScreen
/** /**
@ -44,50 +45,155 @@ class ThreeDotMenuMainTest {
fun homeThreeDotMenuItemsTest() { fun homeThreeDotMenuItemsTest() {
homeScreen { homeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
verifyBookmarksButton() verifyHomeThreeDotMainMenuItems(isRequestDesktopSiteEnabled = false)
verifyHistoryButton() }.openBookmarks {
verifyDownloadsButton() verifyBookmarksMenuView()
verifyAddOnsButton()
// Disabled step due to https://github.com/mozilla-mobile/fenix/issues/26788
// verifySyncSignInButton()
verifyDesktopSite()
verifyWhatsNewButton()
verifyHelpButton()
verifyCustomizeHomeButton()
verifySettingsButton()
}.openSettings {
verifySettingsView()
}.goBack { }.goBack {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openCustomizeHome { }.openHistory {
verifyHomePageView() verifyHistoryMenuView()
}.goBack {
}.openThreeDotMenu {
}.openDownloadsManager {
verifyEmptyDownloadsList()
}.goBack { }.goBack {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openAddonsManagerMenu {
verifyAddonsItems()
}.goBack {
}.openThreeDotMenu {
}.openSyncSignIn {
verifyTurnOnSyncMenu()
}.goBack {
// Desktop toggle
}.openThreeDotMenu {
}.switchDesktopSiteMode {
}
homeScreen {
}.openThreeDotMenu {
verifyDesktopSiteModeEnabled(isRequestDesktopSiteEnabled = true)
}.openWhatsNew {
verifyWhatsNewURL()
}.goToHomescreen {
}.openThreeDotMenu {
}.openHelp { }.openHelp {
verifyHelpUrl() verifyHelpUrl()
}.openTabDrawer { }.goToHomescreen {
closeTab() }.openThreeDotMenu {
}.openCustomizeHome {
verifyHomePageView()
}.goBack {
}.openThreeDotMenu {
}.openSettings {
verifySettingsView()
} }
}
// Verifies the list of items in the homescreen's 3 dot main menu in private browsing
@Test
fun privateHomeThreeDotMenuItemsTest() {
homeScreen { homeScreen {
}.togglePrivateBrowsingMode()
homeScreen {
}.openThreeDotMenu {
verifyHomeThreeDotMainMenuItems(isRequestDesktopSiteEnabled = false)
}.openBookmarks {
verifyBookmarksMenuView()
}.goBack {
}.openThreeDotMenu {
}.openHistory {
verifyHistoryMenuView()
}.goBack {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openDownloadsManager {
verifyEmptyDownloadsList()
}.goBack {
}.openThreeDotMenu {
}.openAddonsManagerMenu {
verifyAddonsItems()
}.goBack {
}.openThreeDotMenu {
}.openSyncSignIn {
verifyTurnOnSyncMenu()
}.goBack {
// Desktop toggle
}.openThreeDotMenu {
}.switchDesktopSiteMode {
}
homeScreen {
}.openThreeDotMenu {
verifyDesktopSiteModeEnabled(isRequestDesktopSiteEnabled = true)
}.openWhatsNew { }.openWhatsNew {
verifyWhatsNewURL() verifyWhatsNewURL()
}.openTabDrawer { }.goToHomescreen {
closeTab() }.openThreeDotMenu {
}.openHelp {
verifyHelpUrl()
}.goToHomescreen {
}.openThreeDotMenu {
}.openCustomizeHome {
verifyHomePageView()
}.goBack {
}.openThreeDotMenu {
}.openSettings {
verifySettingsView()
} }
}
@Test
fun setDesktopSiteBeforePageLoadTest() {
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 4)
homeScreen { homeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openBookmarks { verifyDesktopSiteModeEnabled(false)
verifyBookmarksMenuView() }.switchDesktopSiteMode {
}.closeMenu { }.openNavigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.openThreeDotMenu {
verifyDesktopSiteModeEnabled(true)
}.closeBrowserMenuToBrowser {
clickLinkMatchingText("Link 1")
}.openThreeDotMenu {
verifyDesktopSiteModeEnabled(true)
}.closeBrowserMenuToBrowser {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
longClickLink("Link 2")
clickContextOpenLinkInNewTab()
snackBarButtonClick()
}.openThreeDotMenu {
verifyDesktopSiteModeEnabled(false)
} }
}
@Test
fun privateBrowsingSetDesktopSiteBeforePageLoadTest() {
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 4)
homeScreen {
}.togglePrivateBrowsingMode()
homeScreen { homeScreen {
}.openThreeDotMenu { }.openThreeDotMenu {
}.openHistory { verifyDesktopSiteModeEnabled(false)
verifyHistoryMenuView() }.switchDesktopSiteMode {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
}.openThreeDotMenu {
verifyDesktopSiteModeEnabled(true)
}.closeBrowserMenuToBrowser {
clickLinkMatchingText("Link 1")
}.openThreeDotMenu {
verifyDesktopSiteModeEnabled(true)
}.closeBrowserMenuToBrowser {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(webPage.url) {
longClickLink("Link 2")
clickContextOpenLinkInPrivateTab()
snackBarButtonClick()
}.openThreeDotMenu {
verifyDesktopSiteModeEnabled(false)
} }
} }
} }

@ -9,12 +9,12 @@ import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.Constants.defaultTopSitesList
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RetryTestRule import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
@ -67,7 +67,7 @@ class TopSitesTest {
}.enterURLAndEnterToBrowser(defaultWebPage.url) { }.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu { }.openThreeDotMenu {
expandMenu() expandMenu()
verifyAddToTopSitesButton() verifyAddToShortcutsButton()
}.addToFirefoxHome { }.addToFirefoxHome {
verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts)) verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts))
}.goToHomescreen { }.goToHomescreen {
@ -84,7 +84,7 @@ class TopSitesTest {
}.enterURLAndEnterToBrowser(defaultWebPage.url) { }.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu { }.openThreeDotMenu {
expandMenu() expandMenu()
verifyAddToTopSitesButton() verifyAddToShortcutsButton()
}.addToFirefoxHome { }.addToFirefoxHome {
verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts)) verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts))
}.goToHomescreen { }.goToHomescreen {
@ -111,7 +111,7 @@ class TopSitesTest {
}.enterURLAndEnterToBrowser(defaultWebPage.url) { }.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu { }.openThreeDotMenu {
expandMenu() expandMenu()
verifyAddToTopSitesButton() verifyAddToShortcutsButton()
}.addToFirefoxHome { }.addToFirefoxHome {
verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts)) verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts))
}.goToHomescreen { }.goToHomescreen {
@ -134,7 +134,7 @@ class TopSitesTest {
waitForPageToLoad() waitForPageToLoad()
}.openThreeDotMenu { }.openThreeDotMenu {
expandMenu() expandMenu()
verifyAddToTopSitesButton() verifyAddToShortcutsButton()
}.addToFirefoxHome { }.addToFirefoxHome {
verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts)) verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts))
}.goToHomescreen { }.goToHomescreen {
@ -156,7 +156,7 @@ class TopSitesTest {
}.enterURLAndEnterToBrowser(defaultWebPage.url) { }.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu { }.openThreeDotMenu {
expandMenu() expandMenu()
verifyAddToTopSitesButton() verifyAddToShortcutsButton()
}.addToFirefoxHome { }.addToFirefoxHome {
verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts)) verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts))
}.goToHomescreen { }.goToHomescreen {
@ -169,6 +169,28 @@ class TopSitesTest {
} }
} }
@Test
fun verifyUndoRemoveTopSite() {
val defaultWebPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu {
expandMenu()
verifyAddToShortcutsButton()
}.addToFirefoxHome {
verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts))
}.goToHomescreen {
verifyExistingTopSitesList()
verifyExistingTopSitesTabs(defaultWebPage.title)
}.openContextMenuOnTopSitesWithTitle(defaultWebPage.title) {
verifyTopSiteContextMenuItems()
}.removeTopSite {
clickUndoSnackBarButton()
verifyExistingTopSitesTabs(defaultWebPage.title)
}
}
@Test @Test
fun verifyRemoveTopSiteFromMainMenu() { fun verifyRemoveTopSiteFromMainMenu() {
val defaultWebPage = getGenericAsset(mockWebServer, 1) val defaultWebPage = getGenericAsset(mockWebServer, 1)
@ -177,7 +199,7 @@ class TopSitesTest {
}.enterURLAndEnterToBrowser(defaultWebPage.url) { }.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.openThreeDotMenu { }.openThreeDotMenu {
expandMenu() expandMenu()
verifyAddToTopSitesButton() verifyAddToShortcutsButton()
}.addToFirefoxHome { }.addToFirefoxHome {
verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts)) verifySnackBarText(getStringResource(R.string.snackbar_added_to_shortcuts))
}.goToHomescreen { }.goToHomescreen {
@ -192,21 +214,15 @@ class TopSitesTest {
} }
} }
// Expected for en-us defaults
@Test @Test
fun verifyDefaultTopSitesLocale_EN() { fun verifyDefaultTopSitesList() {
// en-US defaults
val defaultTopSites = arrayOf(
"Top Articles",
"Wikipedia",
"Google",
)
homeScreen { }.dismissOnboarding() homeScreen { }.dismissOnboarding()
homeScreen { homeScreen {
verifyExistingTopSitesList() verifyExistingTopSitesList()
defaultTopSites.forEach { item -> defaultTopSitesList.values.forEach { value ->
verifyExistingTopSitesTabs(item) verifyExistingTopSitesTabs(value)
} }
} }
} }
@ -236,71 +252,4 @@ class TopSitesTest {
verifyEmptyHistoryView() verifyEmptyHistoryView()
} }
} }
@SmokeTest
@Test
fun verifySponsoredShortcutsListTest() {
homeScreen {
var sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
var sponsoredShortcutTitle2 = getSponsoredShortcutTitle(3)
verifyExistingSponsoredTopSitesTabs(sponsoredShortcutTitle, 2)
verifyExistingSponsoredTopSitesTabs(sponsoredShortcutTitle2, 3)
}.openThreeDotMenu {
}.openCustomizeHome {
verifySponsoredShortcutsCheckBox(true)
clickSponsoredShortcuts()
verifySponsoredShortcutsCheckBox(false)
}.goBack {
verifyNotExistingSponsoredTopSitesList()
}
}
@Test
fun openSponsoredShortcutTest() {
var sponsoredShortcutTitle = ""
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
}.openSponsoredShortcut(sponsoredShortcutTitle) {
verifyUrl(sponsoredShortcutTitle)
}
}
@Test
fun openSponsoredShortcutInPrivateBrowsingTest() {
var sponsoredShortcutTitle = ""
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
}.openContextMenuOnSponsoredShortcut(sponsoredShortcutTitle) {
}.openTopSiteInPrivateTab {
verifyUrl(sponsoredShortcutTitle)
}
}
@Ignore("Failing, see: https://github.com/mozilla-mobile/fenix/issues/25926")
@Test
fun verifySponsoredShortcutsSponsorsAndPrivacyOptionTest() {
var sponsoredShortcutTitle = ""
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
}.openContextMenuOnSponsoredShortcut(sponsoredShortcutTitle) {
}.clickSponsorsAndPrivacyButton {
verifyUrl("support.mozilla.org/en-US/kb/sponsor-privacy")
}
}
@Test
fun verifySponsoredShortcutsSettingsOptionTest() {
var sponsoredShortcutTitle = ""
homeScreen {
sponsoredShortcutTitle = getSponsoredShortcutTitle(2)
}.openContextMenuOnSponsoredShortcut(sponsoredShortcutTitle) {
}.clickSponsoredShortcutsSettingsButton {
verifyHomePageView()
}
}
} }

@ -6,24 +6,22 @@ package org.mozilla.fenix.ui.robots
import android.os.Build import android.os.Build
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.clearText
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until
import org.hamcrest.CoreMatchers.allOf
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.MatcherHelper.assertItemContainingTextExists
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import java.util.regex.Pattern import java.util.regex.Pattern
/** /**
@ -37,18 +35,15 @@ class AddToHomeScreenRobot {
fun clickAddPrivateBrowsingShortcutButton() = addPrivateBrowsingShortcutButton().click() fun clickAddPrivateBrowsingShortcutButton() = addPrivateBrowsingShortcutButton().click()
fun addShortcutName(title: String) { fun addShortcutName(title: String) = shortcutTextField.setText(title)
mDevice.waitNotNull(Until.findObject(By.text("Add to Home screen")), waitingTime)
shortcutNameField()
.perform(clearText())
.perform(typeText(title))
}
fun verifyShortcutNameField(expectedText: String) = assertShortcutNameField(expectedText) fun verifyShortcutTextFieldTitle(title: String) = assertItemContainingTextExists(shortcutTitle(title))
fun clickAddShortcutButton() = addButton().click() fun clickAddShortcutButton() =
confirmAddToHomeScreenButton.clickAndWaitForNewWindow(waitingTime)
fun clickCancelShortcutButton() = cancelAddToHomeScreenButton().click() fun clickCancelShortcutButton() =
cancelAddToHomeScreenButton.click()
fun clickAddAutomaticallyButton() { fun clickAddAutomaticallyButton() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -106,22 +101,6 @@ fun addToHomeScreen(interact: AddToHomeScreenRobot.() -> Unit): AddToHomeScreenR
return AddToHomeScreenRobot.Transition() return AddToHomeScreenRobot.Transition()
} }
private fun shortcutNameField() = onView(withId(R.id.shortcut_text))
private fun assertShortcutNameField(expectedText: String) {
onView(
allOf(
withId(R.id.shortcut_text),
withText(expectedText),
),
)
.check(matches(isCompletelyDisplayed()))
}
private fun addButton() = onView((withText("ADD")))
private fun cancelAddToHomeScreenButton() = onView((withText("CANCEL")))
private fun addAutomaticallyButton() = private fun addAutomaticallyButton() =
mDevice.findObject(UiSelector().textContains("add automatically")) mDevice.findObject(UiSelector().textContains("add automatically"))
@ -134,3 +113,12 @@ private fun noThanksPrivateBrowsingShortcutButton() = onView(withId(R.id.cfr_neg
private fun assertNoThanksPrivateBrowsingShortcutButton() = noThanksPrivateBrowsingShortcutButton() private fun assertNoThanksPrivateBrowsingShortcutButton() = noThanksPrivateBrowsingShortcutButton()
.check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) .check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
private val cancelAddToHomeScreenButton =
itemWithResId("$packageName:id/cancel_button")
private val confirmAddToHomeScreenButton =
itemWithResId("$packageName:id/add_button")
private val shortcutTextField =
itemWithResId("$packageName:id/shortcut_text")
private fun shortcutTitle(title: String) =
itemWithResIdAndText("$packageName:id/shortcut_text", title)

@ -258,6 +258,13 @@ class BookmarksRobot {
SettingsTurnOnSyncRobot().interact() SettingsTurnOnSyncRobot().interact()
return SettingsTurnOnSyncRobot.Transition() return SettingsTurnOnSyncRobot.Transition()
} }
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
goBackButton().click()
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
} }
} }

@ -44,6 +44,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
import org.mozilla.fenix.helpers.MatcherHelper
import org.mozilla.fenix.helpers.SessionLoadedIdlingResource import org.mozilla.fenix.helpers.SessionLoadedIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong
@ -911,22 +912,32 @@ class BrowserRobot {
} }
fun openTabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition { fun openTabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
mDevice.waitForObjects( for (i in 1..RETRY_COUNT) {
mDevice.findObject( try {
UiSelector() mDevice.waitForObjects(
.resourceId("$packageName:id/mozac_browser_toolbar_browser_actions"), mDevice.findObject(
), UiSelector()
waitingTime, .resourceId("$packageName:id/mozac_browser_toolbar_browser_actions"),
) ),
waitingTime,
tabsCounter().click() )
mDevice.waitForObjects( tabsCounter().click()
mDevice.findObject( assertTrue(
UiSelector().resourceId("$packageName:id/new_tab_button"), MatcherHelper.itemWithResId("$packageName:id/new_tab_button")
), .waitForExists(waitingTime),
waitingTime, )
)
break
} catch (e: AssertionError) {
if (i == RETRY_COUNT) {
throw e
} else {
mDevice.waitForIdle()
}
}
}
assertTrue(MatcherHelper.itemWithResId("$packageName:id/new_tab_button").waitForExists(waitingTime))
TabDrawerRobot().interact() TabDrawerRobot().interact()
return TabDrawerRobot.Transition() return TabDrawerRobot.Transition()

@ -7,6 +7,7 @@ package org.mozilla.fenix.ui.robots
import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasText import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isDialog
import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.performTouchInput
@ -15,6 +16,7 @@ import androidx.compose.ui.test.swipeRight
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.action.ViewActions.pressImeActionButton import androidx.test.espresso.action.ViewActions.pressImeActionButton
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.uiautomator.By import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiScrollable
@ -23,10 +25,12 @@ import androidx.test.uiautomator.Until
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull import org.mozilla.fenix.helpers.ext.waitNotNull
class CollectionRobot { class CollectionRobot {
@ -57,7 +61,8 @@ class CollectionRobot {
// names a collection saved from tab drawer // names a collection saved from tab drawer
fun typeCollectionNameAndSave(collectionName: String) { fun typeCollectionNameAndSave(collectionName: String) {
collectionNameTextField().text = collectionName collectionNameTextField().text = collectionName
mDevice.findObject(UiSelector().textContains("OK")).click() addCollectionButtonPanel.waitForExists(waitingTime)
addCollectionOkButton.click()
} }
fun verifyTabsSelectedCounterText(numOfTabs: Int) { fun verifyTabsSelectedCounterText(numOfTabs: Int) {
@ -289,3 +294,7 @@ private fun backButton() =
mDevice.findObject( mDevice.findObject(
UiSelector().resourceId("$packageName:id/back_button"), UiSelector().resourceId("$packageName:id/back_button"),
) )
private val addCollectionButtonPanel =
itemWithResId("$packageName:id/buttonPanel")
private val addCollectionOkButton = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog())

@ -123,6 +123,13 @@ class DownloadRobot {
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
} }
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
goBackButton().click()
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
} }
} }
@ -195,3 +202,5 @@ private fun assertDownloadedFileIcon() =
mDevice.findObject(UiSelector().resourceId("$packageName:id/favicon")) mDevice.findObject(UiSelector().resourceId("$packageName:id/favicon"))
.exists(), .exists(),
) )
private fun goBackButton() = onView(withContentDescription("Navigate up"))

@ -37,7 +37,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withHint
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until
@ -54,6 +53,16 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.MatcherHelper.assertCheckedItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemContainingTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndDescriptionExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.checkedItemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndDescription
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.appContext import org.mozilla.fenix.helpers.TestHelper.appContext
@ -76,15 +85,15 @@ class HomeScreenRobot {
" service provider, it makes it easier to keep what you do online private from anyone" + " service provider, it makes it easier to keep what you do online private from anyone" +
" else who uses this device." " else who uses this device."
fun verifyNavigationToolbar() = assertAppItemsWithResourceId(navigationToolbar) fun verifyNavigationToolbar() = assertItemWithResIdExists(navigationToolbar)
fun verifyFocusedNavigationToolbar() = assertFocusedNavigationToolbar() fun verifyFocusedNavigationToolbar() = assertFocusedNavigationToolbar()
fun verifyHomeScreen() = assertAppItemsWithResourceId(homeScreen) fun verifyHomeScreen() = assertItemWithResIdExists(homeScreen)
fun verifyHomeScreenAppBarItems() = fun verifyHomeScreenAppBarItems() =
assertAppItemsWithResourceId(homeScreen, privateBrowsingButton, homepageWordmark) assertItemWithResIdExists(homeScreen, privateBrowsingButton, homepageWordmark)
fun verifyHomeScreenWelcomeItems() = fun verifyHomeScreenWelcomeItems() =
assertAppItemsContainingText(welcomeHeader, welcomeSubHeader) assertItemContainingTextExists(welcomeHeader, welcomeSubHeader)
fun verifyChooseYourThemeCard( fun verifyChooseYourThemeCard(
isDarkThemeChecked: Boolean, isDarkThemeChecked: Boolean,
@ -92,28 +101,37 @@ class HomeScreenRobot {
isAutomaticThemeChecked: Boolean, isAutomaticThemeChecked: Boolean,
) { ) {
scrollToElementByText(getStringResource(R.string.onboarding_theme_picker_header)) scrollToElementByText(getStringResource(R.string.onboarding_theme_picker_header))
assertAppItemsContainingText( assertItemContainingTextExists(
chooseThemeHeader, chooseThemeHeader,
chooseThemeText, chooseThemeText,
darkThemeDescription, darkThemeDescription,
lightThemeDescription, lightThemeDescription,
) )
assertAppItemsStateWithResourceId( assertCheckedItemWithResIdExists(
darkThemeToggle(isDarkThemeChecked), darkThemeToggle(isDarkThemeChecked),
lightThemeToggle(isLightThemeChecked), lightThemeToggle(isLightThemeChecked),
automaticThemeToggle(isAutomaticThemeChecked), automaticThemeToggle(isAutomaticThemeChecked),
) )
assertAppItemsWithResourceIdAndDescription(automaticThemeDescription) assertItemWithResIdAndDescriptionExists(automaticThemeDescription)
} }
fun clickLightThemeButton() =
itemWithResId("$packageName:id/theme_light_radio_button").click()
fun clickDarkThemeButton() =
itemWithResId("$packageName:id/theme_dark_radio_button").click()
fun clickAutomaticThemeButton() =
itemWithResId("$packageName:id/theme_automatic_radio_button").click()
fun verifyToolbarPlacementCard(isBottomChecked: Boolean, isTopChecked: Boolean) { fun verifyToolbarPlacementCard(isBottomChecked: Boolean, isTopChecked: Boolean) {
scrollToElementByText(getStringResource(R.string.onboarding_toolbar_placement_header_1)) scrollToElementByText(getStringResource(R.string.onboarding_toolbar_placement_header_1))
assertAppItemsContainingText(toolbarPlacementHeader, toolbarPlacementDescription) assertItemContainingTextExists(toolbarPlacementHeader, toolbarPlacementDescription)
assertAppItemsStateWithResourceId( assertCheckedItemWithResIdExists(
toolbarPlacementBottomRadioButton(isBottomChecked), toolbarPlacementBottomRadioButton(isBottomChecked),
toolbarPlacementTopRadioButton(isTopChecked), toolbarPlacementTopRadioButton(isTopChecked),
) )
assertAppItemsWithResourceId( assertItemWithResIdExists(
toolbarPlacementBottomImage, toolbarPlacementBottomImage,
toolbarPlacementBottomTitle, toolbarPlacementBottomTitle,
toolbarPlacementTopImage, toolbarPlacementTopImage,
@ -121,48 +139,60 @@ class HomeScreenRobot {
) )
} }
fun clickTopToolbarPlacementButton() =
itemWithResId("$packageName:id/toolbar_top_radio_button").click()
fun clickBottomToolbarPlacementButton() =
itemWithResId("$packageName:id/toolbar_bottom_radio_button").click()
fun verifySignInToSyncCard() { fun verifySignInToSyncCard() {
scrollToElementByText(getStringResource(R.string.onboarding_account_sign_in_header)) scrollToElementByText(getStringResource(R.string.onboarding_account_sign_in_header))
assertAppItemsContainingText(startSyncHeader, startSyncDescription) assertItemContainingTextExists(startSyncHeader, startSyncDescription)
assertAppItemsWithResourceId(signInButton) assertItemWithResIdExists(signInButton)
} }
fun verifyPrivacyProtectionCard(isStandardChecked: Boolean, isStrictChecked: Boolean) { fun verifyPrivacyProtectionCard(isStandardChecked: Boolean, isStrictChecked: Boolean) {
scrollToElementByText(getStringResource(R.string.onboarding_tracking_protection_header)) scrollToElementByText(getStringResource(R.string.onboarding_privacy_notice_header_1))
assertAppItemsContainingText(privacyProtectionHeader, privacyProtectionDescription) assertItemContainingTextExists(privacyProtectionHeader, privacyProtectionDescription)
assertAppItemsStateWithResourceId( assertCheckedItemWithResIdExists(
standardTrackingProtectionToggle(isStandardChecked), standardTrackingProtectionToggle(isStandardChecked),
strictTrackingProtectionToggle(isStrictChecked), strictTrackingProtectionToggle(isStrictChecked),
) )
} }
fun clickStandardTrackingProtectionButton() =
itemWithResId("$packageName:id/tracking_protection_standard_option").click()
fun clickStrictTrackingProtectionButton() =
itemWithResId("$packageName:id/tracking_protection_strict_default").click()
fun verifyPrivacyNoticeCard() { fun verifyPrivacyNoticeCard() {
scrollToElementByText(getStringResource(R.string.onboarding_privacy_notice_header_1)) scrollToElementByText(getStringResource(R.string.onboarding_privacy_notice_header_1))
assertAppItemsContainingText(privacyNoticeHeader, privacyNoticeDescription) assertItemContainingTextExists(privacyNoticeHeader, privacyNoticeDescription)
assertAppItemsWithResourceId(privacyNoticeButton) assertItemWithResIdExists(privacyNoticeButton)
} }
fun verifyStartBrowsingSection() { fun verifyStartBrowsingSection() {
scrollToElementByText(getStringResource(R.string.onboarding_finish)) scrollToElementByText(getStringResource(R.string.onboarding_finish))
assertAppItemsWithResourceId(startBrowsingButton) assertItemWithResIdExists(startBrowsingButton)
assertAppItemsContainingText(conclusionHeader) assertItemContainingTextExists(conclusionHeader)
} }
fun verifyNavigationToolbarItems(numberOfOpenTabs: String) { fun verifyNavigationToolbarItems(numberOfOpenTabs: String) {
assertAppItemsWithResourceId(navigationToolbar, menuButton) assertItemWithResIdExists(navigationToolbar, menuButton)
assertAppItemsWithResourceIdAndText(tabCounter(numberOfOpenTabs)) assertItemWithResIdAndTextExists(tabCounter(numberOfOpenTabs))
} }
fun verifyHomePrivateBrowsingButton() = assertAppItemsWithResourceId(privateBrowsingButton) fun verifyHomePrivateBrowsingButton() = assertItemWithResIdExists(privateBrowsingButton)
fun verifyHomeMenuButton() = assertAppItemsWithResourceId(menuButton) fun verifyHomeMenuButton() = assertItemWithResIdExists(menuButton)
fun verifyTabButton() = assertTabButton() fun verifyTabButton() = assertTabButton()
fun verifyCollectionsHeader() = assertCollectionsHeader() fun verifyCollectionsHeader() = assertCollectionsHeader()
fun verifyNoCollectionsText() = assertNoCollectionsText() fun verifyNoCollectionsText() = assertNoCollectionsText()
fun verifyHomeWordmark() = assertAppItemsWithResourceId(homepageWordmark) fun verifyHomeWordmark() = assertItemWithResIdExists(homepageWordmark)
fun verifyHomeComponent() = assertHomeComponent() fun verifyHomeComponent() = assertHomeComponent()
fun verifyDefaultSearchEngine(searchEngine: String) = verifySearchEngineIcon(searchEngine) fun verifyDefaultSearchEngine(searchEngine: String) = verifySearchEngineIcon(searchEngine)
fun verifyTabCounter(numberOfOpenTabs: String) = fun verifyTabCounter(numberOfOpenTabs: String) =
assertAppItemsWithResourceIdAndText(tabCounter(numberOfOpenTabs)) assertItemWithResIdAndTextExists(tabCounter(numberOfOpenTabs))
fun verifyKeyboardVisible() = assertKeyboardVisibility(isExpectedToBeVisible = true) fun verifyKeyboardVisible() = assertKeyboardVisibility(isExpectedToBeVisible = true)
fun verifyWallpaperImageApplied(isEnabled: Boolean) { fun verifyWallpaperImageApplied(isEnabled: Boolean) {
@ -184,11 +214,14 @@ class HomeScreenRobot {
} }
// First Run elements // First Run elements
fun verifyWelcomeHeader() = assertAppItemsContainingText(welcomeHeader) fun verifyWelcomeHeader() = assertItemContainingTextExists(welcomeHeader)
fun verifyAccountsSignInButton() = assertAppItemsWithResourceId(signInButton) fun verifyAccountsSignInButton() {
scrollToElementByText(getStringResource(R.string.onboarding_account_sign_in_header))
assertItemWithResIdExists(signInButton)
}
fun verifyStartBrowsingButton() { fun verifyStartBrowsingButton() {
scrollToElementByText(getStringResource(R.string.onboarding_finish)) scrollToElementByText(getStringResource(R.string.onboarding_finish))
assertAppItemsWithResourceId(startBrowsingButton) assertItemWithResIdExists(startBrowsingButton)
} }
// Upgrading users onboarding dialog // Upgrading users onboarding dialog
@ -233,9 +266,24 @@ class HomeScreenRobot {
fun verifyExistingTopSitesList() = assertExistingTopSitesList() fun verifyExistingTopSitesList() = assertExistingTopSitesList()
fun verifyNotExistingTopSitesList(title: String) = assertNotExistingTopSitesList(title) fun verifyNotExistingTopSitesList(title: String) = assertNotExistingTopSitesList(title)
fun verifySponsoredShortcutDoesNotExist(sponsoredShortcutTitle: String, position: Int) =
assertFalse(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/top_site_item")
.index(position - 1),
).getChild(
UiSelector()
.textContains(sponsoredShortcutTitle),
).waitForExists(waitingTime),
)
fun verifyNotExistingSponsoredTopSitesList() = assertSponsoredTopSitesNotDisplayed() fun verifyNotExistingSponsoredTopSitesList() = assertSponsoredTopSitesNotDisplayed()
fun verifyExistingTopSitesTabs(title: String) = assertExistingTopSitesTabs(title) fun verifyExistingTopSitesTabs(title: String) = assertExistingTopSitesTabs(title)
fun verifyExistingSponsoredTopSitesTabs(sponsoredShortcutTitle: String, position: Int) = assertSponsoredTopSiteIsDisplayed(sponsoredShortcutTitle, position) fun verifySponsoredShortcutDetails(sponsoredShortcutTitle: String, position: Int) {
assertSponsoredShortcutLogoIsDisplayed(position)
assertSponsoredShortcutTitle(sponsoredShortcutTitle, position)
assertSponsoredSubtitleIsDisplayed(position)
}
fun verifyTopSiteContextMenuItems() = assertTopSiteContextMenuItems() fun verifyTopSiteContextMenuItems() = assertTopSiteContextMenuItems()
fun verifyJumpBackInSectionIsDisplayed() = assertJumpBackInSectionIsDisplayed() fun verifyJumpBackInSectionIsDisplayed() = assertJumpBackInSectionIsDisplayed()
@ -293,14 +341,7 @@ class HomeScreenRobot {
mDevice.waitNotNull(findObject(By.text(expectedText)), waitingTime) mDevice.waitNotNull(findObject(By.text(expectedText)), waitingTime)
} }
fun clickUndoCollectionDeletion(expectedText: String) { fun clickUndoSnackBarButton() = undoSnackBarButton.click()
onView(
allOf(
withId(R.id.snackbar_btn),
withText(expectedText),
),
).click()
}
fun clickFirefoxLogo() = homepageWordmark.click() fun clickFirefoxLogo() = homepageWordmark.click()
@ -429,19 +470,6 @@ class HomeScreenRobot {
} }
} }
fun getSponsoredShortcutTitle(position: Int): String {
val sponsoredShortcut = mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/top_site_item")
.index(position - 1),
).getChild(
UiSelector()
.resourceId("$packageName:id/top_site_title"),
).text
return sponsoredShortcut
}
fun verifyJumpBackInMessage() { fun verifyJumpBackInMessage() {
assertTrue( assertTrue(
mDevice.findObject( mDevice.findObject(
@ -683,6 +711,7 @@ class HomeScreenRobot {
} }
fun clickSaveTabsToCollectionButton(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition { fun clickSaveTabsToCollectionButton(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
scrollToElementByText(getStringResource(R.string.no_collections_description2))
saveTabsToCollectionButton().click() saveTabsToCollectionButton().click()
TabDrawerRobot().interact() TabDrawerRobot().interact()
@ -780,6 +809,20 @@ class HomeScreenRobot {
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
} }
fun clickSignInButton(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
signInButton.clickAndWaitForNewWindow(waitingTimeShort)
SyncSignInRobot().interact()
return SyncSignInRobot.Transition()
}
fun clickPrivacyNoticeButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
privacyNoticeButton.clickAndWaitForNewWindow(waitingTimeShort)
BrowserRobot().interact()
return BrowserRobot.Transition()
}
} }
} }
@ -874,10 +917,17 @@ private fun assertExistingTopSitesTabs(title: String) {
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
} }
private fun assertSponsoredTopSiteIsDisplayed(sponsoredShortcutTitle: String, position: Int) { private fun assertSponsoredShortcutLogoIsDisplayed(position: Int) =
assertSponsoredShortcutTitle(sponsoredShortcutTitle, position) assertTrue(
assertSponsoredSubtitleIsDisplayed(position) mDevice.findObject(
} UiSelector()
.resourceId("$packageName:id/top_site_item")
.index(position - 1),
).getChild(
UiSelector()
.resourceId("$packageName:id/favicon_card"),
).waitForExists(waitingTime),
)
private fun assertSponsoredSubtitleIsDisplayed(position: Int) = private fun assertSponsoredSubtitleIsDisplayed(position: Int) =
assertTrue( assertTrue(
@ -891,7 +941,7 @@ private fun assertSponsoredSubtitleIsDisplayed(position: Int) =
).waitForExists(waitingTime), ).waitForExists(waitingTime),
) )
private fun assertSponsoredShortcutTitle(sponsoredShortcutTitle: String, position: Int) { private fun assertSponsoredShortcutTitle(sponsoredShortcutTitle: String, position: Int) =
assertTrue( assertTrue(
mDevice.findObject( mDevice.findObject(
UiSelector() UiSelector()
@ -902,7 +952,6 @@ private fun assertSponsoredShortcutTitle(sponsoredShortcutTitle: String, positio
.textContains(sponsoredShortcutTitle), .textContains(sponsoredShortcutTitle),
).waitForExists(waitingTime), ).waitForExists(waitingTime),
) )
}
private fun assertNotExistingTopSitesList(title: String) { private fun assertNotExistingTopSitesList(title: String) {
mDevice.findObject(UiSelector().text(title)).waitUntilGone(waitingTime) mDevice.findObject(UiSelector().text(title)).waitUntilGone(waitingTime)
@ -1010,125 +1059,82 @@ private fun sponsoredShortcut(sponsoredShortcutTitle: String) =
private fun storyByTopicItem(composeTestRule: ComposeTestRule, position: Int) = private fun storyByTopicItem(composeTestRule: ComposeTestRule, position: Int) =
composeTestRule.onNodeWithTag("pocket.categories").onChildAt(position - 1) composeTestRule.onNodeWithTag("pocket.categories").onChildAt(position - 1)
private fun appItemWithResourceId(resourceId: String) =
mDevice.findObject(UiSelector().resourceId(resourceId))
private fun appItemContainingText(itemText: String) =
mDevice.findObject(UiSelector().textContains(itemText))
private fun appItemStateWithResourceId(resourceId: String, state: Boolean) =
mDevice.findObject(UiSelector().resourceId(resourceId).checked(state))
private fun appItemWithResourceIdAndDescription(resourceId: String, description: String) =
mDevice.findObject(UiSelector().resourceId(resourceId).descriptionContains(description))
private fun appItemWithResourceIdAndText(resourceId: String, text: String) =
mDevice.findObject(UiSelector().resourceId(resourceId).text(text))
private fun assertAppItemsWithResourceId(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
private fun assertAppItemsContainingText(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
private fun assertAppItemsStateWithResourceId(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
private fun assertAppItemsWithResourceIdAndDescription(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
private fun assertAppItemsWithResourceIdAndText(vararg appItems: UiObject) {
for (appItem in appItems) {
assertTrue(appItem.waitForExists(waitingTime))
}
}
private val homeScreen = private val homeScreen =
appItemWithResourceId("$packageName:id/homeLayout") itemWithResId("$packageName:id/homeLayout")
private val privateBrowsingButton = private val privateBrowsingButton =
appItemWithResourceId("$packageName:id/privateBrowsingButton") itemWithResId("$packageName:id/privateBrowsingButton")
private val homepageWordmark = private val homepageWordmark =
appItemWithResourceId("$packageName:id/wordmark") itemWithResId("$packageName:id/wordmark")
private val welcomeHeader = appItemContainingText(getStringResource(R.string.onboarding_header_2)) private val welcomeHeader = itemContainingText(getStringResource(R.string.onboarding_header_2))
private val welcomeSubHeader = private val welcomeSubHeader =
appItemContainingText(getStringResource(R.string.onboarding_message)) itemContainingText(getStringResource(R.string.onboarding_message))
private val chooseThemeHeader = private val chooseThemeHeader =
appItemContainingText(getStringResource(R.string.onboarding_theme_picker_header)) itemContainingText(getStringResource(R.string.onboarding_theme_picker_header))
private val chooseThemeText = private val chooseThemeText =
appItemContainingText(getStringResource(R.string.onboarding_theme_picker_description_2)) itemContainingText(getStringResource(R.string.onboarding_theme_picker_description_2))
private val darkThemeDescription = private val darkThemeDescription =
appItemContainingText(getStringResource(R.string.onboarding_theme_dark_title)) itemContainingText(getStringResource(R.string.onboarding_theme_dark_title))
private val lightThemeDescription = private val lightThemeDescription =
appItemContainingText(getStringResource(R.string.onboarding_theme_light_title)) itemContainingText(getStringResource(R.string.onboarding_theme_light_title))
private val automaticThemeDescription = private val automaticThemeDescription =
appItemWithResourceIdAndDescription( itemWithResIdAndDescription(
"$packageName:id/clickable_region_automatic", "$packageName:id/clickable_region_automatic",
"${getStringResource(R.string.onboarding_theme_automatic_title)} ${getStringResource(R.string.onboarding_theme_automatic_summary)}", "${getStringResource(R.string.onboarding_theme_automatic_title)} ${getStringResource(R.string.onboarding_theme_automatic_summary)}",
) )
private fun darkThemeToggle(isChecked: Boolean) = private fun darkThemeToggle(isChecked: Boolean) =
appItemStateWithResourceId("$packageName:id/theme_dark_radio_button", isChecked) checkedItemWithResId("$packageName:id/theme_dark_radio_button", isChecked)
private fun lightThemeToggle(isChecked: Boolean) = private fun lightThemeToggle(isChecked: Boolean) =
appItemStateWithResourceId("$packageName:id/theme_light_radio_button", isChecked) checkedItemWithResId("$packageName:id/theme_light_radio_button", isChecked)
private fun automaticThemeToggle(isChecked: Boolean) = private fun automaticThemeToggle(isChecked: Boolean) =
appItemStateWithResourceId("$packageName:id/theme_automatic_radio_button", isChecked) checkedItemWithResId("$packageName:id/theme_automatic_radio_button", isChecked)
private val toolbarPlacementHeader = private val toolbarPlacementHeader =
appItemContainingText(getStringResource(R.string.onboarding_toolbar_placement_header_1)) itemContainingText(getStringResource(R.string.onboarding_toolbar_placement_header_1))
private val toolbarPlacementDescription = private val toolbarPlacementDescription =
appItemContainingText(getStringResource(R.string.onboarding_toolbar_placement_description)) itemContainingText(getStringResource(R.string.onboarding_toolbar_placement_description))
private fun toolbarPlacementBottomRadioButton(isChecked: Boolean) = private fun toolbarPlacementBottomRadioButton(isChecked: Boolean) =
appItemStateWithResourceId("$packageName:id/toolbar_bottom_radio_button", isChecked) checkedItemWithResId("$packageName:id/toolbar_bottom_radio_button", isChecked)
private fun toolbarPlacementTopRadioButton(isChecked: Boolean) = private fun toolbarPlacementTopRadioButton(isChecked: Boolean) =
appItemStateWithResourceId("$packageName:id/toolbar_top_radio_button", isChecked) checkedItemWithResId("$packageName:id/toolbar_top_radio_button", isChecked)
private val toolbarPlacementBottomImage = private val toolbarPlacementBottomImage =
appItemWithResourceId("$packageName:id/toolbar_bottom_image") itemWithResId("$packageName:id/toolbar_bottom_image")
private val toolbarPlacementBottomTitle = private val toolbarPlacementBottomTitle =
appItemWithResourceId("$packageName:id/toolbar_bottom_title") itemWithResId("$packageName:id/toolbar_bottom_title")
private val toolbarPlacementTopTitle = private val toolbarPlacementTopTitle =
appItemWithResourceId("$packageName:id/toolbar_top_title") itemWithResId("$packageName:id/toolbar_top_title")
private val toolbarPlacementTopImage = private val toolbarPlacementTopImage =
appItemWithResourceId("$packageName:id/toolbar_top_image") itemWithResId("$packageName:id/toolbar_top_image")
private val startSyncHeader = private val startSyncHeader =
appItemContainingText(getStringResource(R.string.onboarding_account_sign_in_header)) itemContainingText(getStringResource(R.string.onboarding_account_sign_in_header))
private val startSyncDescription = private val startSyncDescription =
appItemContainingText(getStringResource(R.string.onboarding_manual_sign_in_description)) itemContainingText(getStringResource(R.string.onboarding_manual_sign_in_description))
private val signInButton = private val signInButton =
appItemWithResourceId("$packageName:id/fxa_sign_in_button") itemWithResId("$packageName:id/fxa_sign_in_button")
private val privacyProtectionHeader = private val privacyProtectionHeader =
appItemContainingText(getStringResource(R.string.onboarding_tracking_protection_header)) itemContainingText(getStringResource(R.string.onboarding_tracking_protection_header))
private val privacyProtectionDescription = private val privacyProtectionDescription =
appItemContainingText(getStringResource(R.string.onboarding_tracking_protection_description)) itemContainingText(getStringResource(R.string.onboarding_tracking_protection_description))
private fun standardTrackingProtectionToggle(isChecked: Boolean) = private fun standardTrackingProtectionToggle(isChecked: Boolean) =
appItemStateWithResourceId("$packageName:id/tracking_protection_standard_option", isChecked) checkedItemWithResId("$packageName:id/tracking_protection_standard_option", isChecked)
private fun strictTrackingProtectionToggle(isChecked: Boolean) = private fun strictTrackingProtectionToggle(isChecked: Boolean) =
appItemStateWithResourceId("$packageName:id/tracking_protection_strict_default", isChecked) checkedItemWithResId("$packageName:id/tracking_protection_strict_default", isChecked)
private val privacyNoticeHeader = private val privacyNoticeHeader =
appItemContainingText(getStringResource(R.string.onboarding_privacy_notice_header_1)) itemContainingText(getStringResource(R.string.onboarding_privacy_notice_header_1))
private val privacyNoticeDescription = private val privacyNoticeDescription =
appItemContainingText(getStringResource(R.string.onboarding_privacy_notice_description)) itemContainingText(getStringResource(R.string.onboarding_privacy_notice_description))
private val privacyNoticeButton = private val privacyNoticeButton =
appItemWithResourceId("$packageName:id/read_button") itemWithResId("$packageName:id/read_button")
private val startBrowsingButton = private val startBrowsingButton =
appItemWithResourceId("$packageName:id/finish_button") itemWithResId("$packageName:id/finish_button")
private val conclusionHeader = private val conclusionHeader =
appItemContainingText(getStringResource(R.string.onboarding_conclusion_header)) itemContainingText(getStringResource(R.string.onboarding_conclusion_header))
private val navigationToolbar = private val navigationToolbar =
appItemWithResourceId("$packageName:id/toolbar") itemWithResId("$packageName:id/toolbar")
private val menuButton = private val menuButton =
appItemWithResourceId("$packageName:id/menuButton") itemWithResId("$packageName:id/menuButton")
private fun tabCounter(numberOfOpenTabs: String) = private fun tabCounter(numberOfOpenTabs: String) =
appItemWithResourceIdAndText("$packageName:id/counter_text", numberOfOpenTabs) itemWithResIdAndText("$packageName:id/counter_text", numberOfOpenTabs)
private val undoSnackBarButton =
itemWithResId("$packageName:id/snackbar_btn")
val deleteFromHistory = val deleteFromHistory =
onView( onView(

@ -46,6 +46,7 @@ import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.isPackageInstalled import org.mozilla.fenix.helpers.TestHelper.isPackageInstalled
import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.waitForObjects
import org.mozilla.fenix.helpers.click import org.mozilla.fenix.helpers.click
/** /**
@ -110,31 +111,40 @@ class SearchRobot {
} }
} }
fun verifyFirefoxSuggestResults(rule: ComposeTestRule, searchSuggestion: String) { fun verifyFirefoxSuggestResults(rule: ComposeTestRule, searchTerm: String, vararg searchSuggestions: String) {
rule.waitForIdle() rule.waitForIdle()
for (i in 1..RETRY_COUNT) { for (i in 1..RETRY_COUNT) {
try { try {
rule.onNodeWithTag("mozac.awesomebar.suggestions") for (searchSuggestion in searchSuggestions) {
.performScrollToNode(hasText(searchSuggestion)) mDevice.waitForObjects(mDevice.findObject(UiSelector().textContains(searchSuggestion)))
.assertExists() rule.onNodeWithTag("mozac.awesomebar.suggestions")
.performScrollToNode(hasText(searchSuggestion))
.assertExists()
}
break break
} catch (e: AssertionError) { } catch (e: AssertionError) {
if (i == RETRY_COUNT) { if (i == RETRY_COUNT) {
throw e throw e
} else { } else {
expandSearchSuggestionsList() mDevice.pressBack()
homeScreen {
}.openSearch {
typeSearch(searchTerm)
}
} }
} }
} }
} }
fun verifyNoSuggestionsAreDisplayed(rule: ComposeTestRule, searchSuggestion: String) { fun verifyNoSuggestionsAreDisplayed(rule: ComposeTestRule, vararg searchSuggestions: String) {
rule.waitForIdle() rule.waitForIdle()
for (searchSuggestion in searchSuggestions) {
assertFalse( assertFalse(
mDevice.findObject(UiSelector().textContains(searchSuggestion)) mDevice.findObject(UiSelector().textContains(searchSuggestion))
.waitForExists(waitingTime), .waitForExists(waitingTime),
) )
}
} }
fun verifyAllowSuggestionsInPrivateModeDialog() { fun verifyAllowSuggestionsInPrivateModeDialog() {
@ -282,10 +292,6 @@ class SearchRobot {
assertTranslatedFocusedNavigationToolbar(toolbarHintString) assertTranslatedFocusedNavigationToolbar(toolbarHintString)
fun verifySearchEngineShortcuts(rule: ComposeTestRule, vararg searchEngines: String) { fun verifySearchEngineShortcuts(rule: ComposeTestRule, vararg searchEngines: String) {
mDevice.findObject(
UiSelector().resourceId("$packageName:id/awesome_bar"),
).swipeUp(1)
for (searchEngine in searchEngines) { for (searchEngine in searchEngines) {
rule.waitForIdle() rule.waitForIdle()
rule.onNodeWithText(searchEngine).assertIsDisplayed() rule.onNodeWithText(searchEngine).assertIsDisplayed()
@ -365,14 +371,21 @@ class SearchRobot {
rule: ComposeTestRule, rule: ComposeTestRule,
interact: SettingsSubMenuSearchRobot.() -> Unit, interact: SettingsSubMenuSearchRobot.() -> Unit,
): SettingsSubMenuSearchRobot.Transition { ): SettingsSubMenuSearchRobot.Transition {
rule.onNodeWithText("Search engine settings") rule.onNodeWithText("Search engine settings").performClick()
.assertIsDisplayed()
.assertHasClickAction()
.performClick()
SettingsSubMenuSearchRobot().interact() SettingsSubMenuSearchRobot().interact()
return SettingsSubMenuSearchRobot.Transition() return SettingsSubMenuSearchRobot.Transition()
} }
fun clickSearchSuggestion(searchSuggestion: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.findObject(UiSelector().textContains(searchSuggestion)).also {
it.waitForExists(waitingTime)
it.clickAndWaitForNewWindow(waitingTimeShort)
}
BrowserRobot().interact()
return BrowserRobot.Transition()
}
} }
} }

@ -4,6 +4,9 @@
package org.mozilla.fenix.ui.robots package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.UiSelector
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -12,6 +15,7 @@ import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.click
class SettingsSubMenuAutofillRobot { class SettingsSubMenuAutofillRobot {
@ -22,14 +26,10 @@ class SettingsSubMenuAutofillRobot {
deleteAddressButton.waitForExists(waitingTime) deleteAddressButton.waitForExists(waitingTime)
deleteAddressButton.click() deleteAddressButton.click()
} }
fun clickCancelDeleteAddressButton() { fun clickCancelDeleteAddressButton() = cancelDeleteAddressButton.click()
cancelDeleteAddressButton.waitForExists(waitingTime)
cancelDeleteAddressButton.click() fun clickConfirmDeleteAddressButton() = confirmDeleteAddressButton.click()
}
fun clickConfirmDeleteAddressButton() {
confirmDeleteAddressButton.waitForExists(waitingTime)
confirmDeleteAddressButton.click()
}
fun clickSubRegionOption(subRegion: String) { fun clickSubRegionOption(subRegion: String) {
subRegionOption(subRegion).waitForExists(waitingTime) subRegionOption(subRegion).waitForExists(waitingTime)
subRegionOption(subRegion).click() subRegionOption(subRegion).click()
@ -81,10 +81,7 @@ class SettingsSubMenuAutofillRobot {
deleteCreditCardButton.click() deleteCreditCardButton.click()
} }
fun clickConfirmDeleteCreditCardButton() { fun clickConfirmDeleteCreditCardButton() = confirmDeleteCreditCardButton.click()
confirmDeleteCreditCardButton.waitForExists(waitingTime)
confirmDeleteCreditCardButton.click()
}
fun clickExpiryMonthOption(expiryMonth: String) { fun clickExpiryMonthOption(expiryMonth: String) {
expiryMonthOption(expiryMonth).waitForExists(waitingTime) expiryMonthOption(expiryMonth).waitForExists(waitingTime)
@ -142,8 +139,8 @@ private val phoneTextInput = mDevice.findObject(UiSelector().resourceId("$packag
private val emailTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/email_input")) private val emailTextInput = mDevice.findObject(UiSelector().resourceId("$packageName:id/email_input"))
private val saveButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/save_button")) private val saveButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/save_button"))
private val deleteAddressButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/delete_address_button")) private val deleteAddressButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/delete_address_button"))
private val cancelDeleteAddressButton = mDevice.findObject(UiSelector().resourceId("android:id/button2")) private val cancelDeleteAddressButton = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog())
private val confirmDeleteAddressButton = mDevice.findObject(UiSelector().resourceId("android:id/button1")) private val confirmDeleteAddressButton = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog())
private val addCreditCardButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_add_credit_card))) private val addCreditCardButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_add_credit_card)))
private val manageSavedCardsButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_manage_saved_cards))) private val manageSavedCardsButton = mDevice.findObject(UiSelector().textContains(getStringResource(R.string.preferences_credit_cards_manage_saved_cards)))
@ -153,8 +150,8 @@ private val expiryMonthDropDown = mDevice.findObject(UiSelector().resourceId("$p
private val expiryYearDropDown = mDevice.findObject(UiSelector().resourceId("$packageName:id/expiry_year_drop_down")) private val expiryYearDropDown = mDevice.findObject(UiSelector().resourceId("$packageName:id/expiry_year_drop_down"))
private val savedCreditCardNumber = mDevice.findObject(UiSelector().resourceId("$packageName:id/credit_card_logo")) private val savedCreditCardNumber = mDevice.findObject(UiSelector().resourceId("$packageName:id/credit_card_logo"))
private val deleteCreditCardButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/delete_credit_card_button")) private val deleteCreditCardButton = mDevice.findObject(UiSelector().resourceId("$packageName:id/delete_credit_card_button"))
private val confirmDeleteCreditCardButton = mDevice.findObject(UiSelector().resourceId("android:id/button1")) private val confirmDeleteCreditCardButton = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog())
private val securedCreditCardsLaterButton = mDevice.findObject(UiSelector().resourceId("android:id/button2")) private val securedCreditCardsLaterButton = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog())
private fun savedAddress(firstName: String) = mDevice.findObject(UiSelector().textContains(firstName)) private fun savedAddress(firstName: String) = mDevice.findObject(UiSelector().textContains(firstName))
private fun subRegionOption(subRegion: String) = mDevice.findObject(UiSelector().textContains(subRegion)) private fun subRegionOption(subRegion: String) = mDevice.findObject(UiSelector().textContains(subRegion))

@ -20,8 +20,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Matchers.endsWith import org.hamcrest.Matchers.endsWith
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.TestHelper.getStringResource import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.hasCousin import org.mozilla.fenix.helpers.TestHelper.hasCousin
@ -35,11 +33,6 @@ class SettingsSubMenuCustomizeRobot {
fun verifyThemes() = assertThemes() fun verifyThemes() = assertThemes()
fun verifyLightThemeApplied(expected: Boolean) =
assertFalse("Light theme not selected", expected)
fun verifyDarkThemeApplied(expected: Boolean) = assertTrue("Dark theme not selected", expected)
fun selectDarkMode() = darkModeToggle().click() fun selectDarkMode() = darkModeToggle().click()
fun selectLightMode() = lightModeToggle().click() fun selectLightMode() = lightModeToggle().click()

@ -6,6 +6,11 @@
package org.mozilla.fenix.ui.robots package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.UiSelector
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
@ -50,8 +55,8 @@ private fun assertSecureConnectionSubMenu(pageTitle: String = "", url: String =
private fun assertClearSiteDataPrompt(url: String) { private fun assertClearSiteDataPrompt(url: String) {
assertTrue(clearSiteDataPrompt(url).waitForExists(waitingTime)) assertTrue(clearSiteDataPrompt(url).waitForExists(waitingTime))
assertTrue(cancelClearSiteDataButton.waitForExists(waitingTime)) cancelClearSiteDataButton.check(matches(isDisplayed()))
assertTrue(deleteSiteDataButton.waitForExists(waitingTime)) deleteSiteDataButton.check(matches(isDisplayed()))
} }
private fun quickActionSheet() = private fun quickActionSheet() =
@ -145,5 +150,5 @@ private fun clearSiteDataPrompt(url: String) =
.textContains(url), .textContains(url),
) )
private val cancelClearSiteDataButton = mDevice.findObject(UiSelector().resourceId("android:id/button2")) private val cancelClearSiteDataButton = onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog())
private val deleteSiteDataButton = mDevice.findObject(UiSelector().resourceId("android:id/button1")) private val deleteSiteDataButton = onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog())

@ -14,9 +14,7 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.UiController import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.GeneralLocation import androidx.test.espresso.action.GeneralLocation
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.longClick
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers import androidx.test.espresso.matcher.RootMatchers
@ -32,7 +30,6 @@ import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until
import androidx.test.uiautomator.Until.findObject import androidx.test.uiautomator.Until.findObject
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import junit.framework.AssertionFailedError
import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue import junit.framework.TestCase.assertTrue
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
@ -115,7 +112,7 @@ class TabDrawerRobot {
var retries = 0 // number of retries before failing, will stop at 2 var retries = 0 // number of retries before failing, will stop at 2
while (!tabItem(title).waitUntilGone(waitingTimeShort) && retries < 3 while (!tabItem(title).waitUntilGone(waitingTimeShort) && retries < 3
) { ) {
tab(title).perform(ViewActions.swipeRight()) tab(title).swipeRight(3)
retries++ retries++
} }
} }
@ -124,7 +121,7 @@ class TabDrawerRobot {
var retries = 0 // number of retries before failing, will stop at 2 var retries = 0 // number of retries before failing, will stop at 2
while (!tabItem(title).waitUntilGone(waitingTimeShort) && retries < 3 while (!tabItem(title).waitUntilGone(waitingTimeShort) && retries < 3
) { ) {
tab(title).perform(ViewActions.swipeLeft()) tab(title).swipeLeft(3)
retries++ retries++
} }
} }
@ -149,75 +146,14 @@ class TabDrawerRobot {
snackBarButton.click() snackBarButton.click()
} }
fun verifyTabMediaControlButtonState(action: String) { fun verifyTabMediaControlButtonState(action: String) =
try { assertTrue(tabMediaControlButton(action).waitForExists(waitingTime))
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_tray_empty_view"),
).waitUntilGone(waitingTime)
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_tray_grid_item"),
).waitForExists(waitingTime)
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/play_pause_button")
.descriptionContains(action),
).waitForExists(waitingTime)
assertTrue(
mDevice.findObject(UiSelector().descriptionContains(action))
.waitForExists(waitingTime),
)
} catch (e: AssertionFailedError) {
// In some cases the tab media button isn't updated after performing an action on it
println("Failed to update the state of the tab media button")
// Let's dismiss the tabs tray and try again
mDevice.pressBack()
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/toolbar"),
).waitForExists(waitingTime)
browserScreen {
}.openTabDrawer {
// Click again the tab media button
tabMediaControlButton().click()
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_tray_empty_view"),
).waitUntilGone(waitingTime)
mDevice.findObject(
UiSelector().resourceId("$packageName:id/tab_tray_grid_item"),
).waitForExists(waitingTime)
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/play_pause_button")
.descriptionContains(action),
).waitForExists(waitingTime)
assertTrue(
mDevice.findObject(UiSelector().descriptionContains(action))
.waitForExists(waitingTime),
)
}
}
}
fun clickTabMediaControlButton(action: String) { fun clickTabMediaControlButton(action: String) {
mDevice.waitNotNull( tabMediaControlButton(action).also {
Until.findObjects( it.waitForExists(waitingTime)
By it.click()
.res("$packageName:id/play_pause_button") }
.descContains(action),
),
waitingTime,
)
tabMediaControlButton().click()
} }
fun clickSelectTabsOption() { fun clickSelectTabsOption() {
@ -245,7 +181,10 @@ class TabDrawerRobot {
waitingTime, waitingTime,
) )
tab(title).perform(longClick()) tab(title).also {
it.waitForExists(waitingTime)
it.longClick()
}
} }
fun createCollection( fun createCollection(
@ -449,8 +388,8 @@ fun tabDrawer(interact: TabDrawerRobot.() -> Unit): TabDrawerRobot.Transition {
return TabDrawerRobot.Transition() return TabDrawerRobot.Transition()
} }
private fun tabMediaControlButton() = private fun tabMediaControlButton(action: String) =
mDevice.findObject(UiSelector().resourceId("$packageName:id/play_pause_button")) mDevice.findObject(UiSelector().descriptionContains(action))
private fun closeTabButton() = private fun closeTabButton() =
mDevice.findObject(UiSelector().descriptionContains("Close tab")) mDevice.findObject(UiSelector().descriptionContains("Close tab"))
@ -459,9 +398,9 @@ private fun assertCloseTabsButton(title: String) =
assertTrue( assertTrue(
mDevice.findObject( mDevice.findObject(
UiSelector() UiSelector()
.resourceId("$packageName:id/mozac_browser_tabstray_close") .descriptionContains("Close tab"),
.descriptionContains("Close tab $title"), ).getFromParent(UiSelector().textContains(title))
).waitForExists(waitingTime), .waitForExists(waitingTime),
) )
private fun normalBrowsingButton() = onView( private fun normalBrowsingButton() = onView(
@ -622,12 +561,7 @@ private val tabsList =
// This Espresso tab selector is used for actions that UIAutomator doesn't handle very well: swipe and long-tap // This Espresso tab selector is used for actions that UIAutomator doesn't handle very well: swipe and long-tap
private fun tab(title: String) = private fun tab(title: String) =
onView( mDevice.findObject(UiSelector().textContains(title))
allOf(
withId(R.id.mozac_browser_tabstray_title),
withText(title),
),
)
// This tab selector is used for actions that involve waiting and asserting the existence of the view // This tab selector is used for actions that involve waiting and asserting the existence of the view
private fun tabItem(title: String) = private fun tabItem(title: String) =

@ -16,21 +16,27 @@ import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.Visibility import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObjectNotFoundException
import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until import androidx.test.uiautomator.Until
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.not
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
import org.mozilla.fenix.helpers.MatcherHelper.assertCheckedItemWithResIdAndTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemContainingTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithDescriptionExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndTextExists
import org.mozilla.fenix.helpers.MatcherHelper.checkedItemWithResIdAndText
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithDescription
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeLong
import org.mozilla.fenix.helpers.TestHelper.getStringResource import org.mozilla.fenix.helpers.TestHelper.getStringResource
@ -45,20 +51,12 @@ import org.mozilla.fenix.helpers.ext.waitNotNull
@Suppress("ForbiddenComment") @Suppress("ForbiddenComment")
class ThreeDotMenuMainRobot { class ThreeDotMenuMainRobot {
fun verifyShareAllTabsButton() = assertShareAllTabsButton() fun verifyShareAllTabsButton() = assertShareAllTabsButton()
fun verifySettingsButton() = assertSettingsButton() fun verifySettingsButton() = assertItemContainingTextExists(settingsButton())
fun verifyCustomizeHomeButton() = assertCustomizeHomeButton() fun verifyHistoryButton() = assertItemContainingTextExists(historyButton)
fun verifyAddOnsButton() = assertAddOnsButton()
fun verifyHistoryButton() = assertHistoryButton()
fun verifyBookmarksButton() = assertBookmarksButton()
fun verifySyncSignInButton() = assertSyncSignInButton()
fun verifyHelpButton() = assertHelpButton()
fun verifyThreeDotMenuExists() = threeDotMenuRecyclerViewExists() fun verifyThreeDotMenuExists() = threeDotMenuRecyclerViewExists()
fun verifyForwardButton() = assertForwardButton() fun verifyAddBookmarkButton() = assertItemWithResIdAndTextExists(addBookmarkButton)
fun verifyAddBookmarkButton() = assertAddBookmarkButton()
fun verifyEditBookmarkButton() = assertEditBookmarkButton() fun verifyEditBookmarkButton() = assertEditBookmarkButton()
fun verifyRefreshButton() = assertRefreshButton()
fun verifyCloseAllTabsButton() = assertCloseAllTabsButton() fun verifyCloseAllTabsButton() = assertCloseAllTabsButton()
fun verifyShareButton() = assertShareButton()
fun verifyReaderViewAppearance(visible: Boolean) = assertReaderViewAppearanceButton(visible) fun verifyReaderViewAppearance(visible: Boolean) = assertReaderViewAppearanceButton(visible)
fun expandMenu() { fun expandMenu() {
@ -66,49 +64,61 @@ class ThreeDotMenuMainRobot {
} }
fun verifyShareTabButton() = assertShareTabButton() fun verifyShareTabButton() = assertShareTabButton()
fun verifySaveCollection() = assertSaveCollectionButton()
fun verifySelectTabs() = assertSelectTabsButton() fun verifySelectTabs() = assertSelectTabsButton()
fun verifyFindInPageButton() = assertFindInPageButton() fun verifyFindInPageButton() = assertItemContainingTextExists(findInPageButton)
fun verifyWhatsNewButton() = assertWhatsNewButton() fun verifyAddToShortcutsButton() = assertItemContainingTextExists(addToShortcutsButton)
fun verifyAddToTopSitesButton() = assertAddToTopSitesButton()
fun verifyRemoveFromShortcutsButton() = assertRemoveFromShortcutsButton() fun verifyRemoveFromShortcutsButton() = assertRemoveFromShortcutsButton()
fun verifyAddToMobileHome() = assertAddToMobileHome()
fun verifyDesktopSite() = assertDesktopSite()
fun verifyDownloadsButton() = assertDownloadsButton()
fun verifyShareTabsOverlay() = assertShareTabsOverlay() fun verifyShareTabsOverlay() = assertShareTabsOverlay()
fun verifyNewTabButton() = assertNormalBrowsingNewTabButton()
fun verifyReportSiteIssueButton() = assertReportSiteIssueButton()
fun verifyDesktopSiteModeEnabled(state: Boolean) { fun verifyDesktopSiteModeEnabled(isRequestDesktopSiteEnabled: Boolean) {
expandMenu() expandMenu()
if (state) { assertCheckedItemWithResIdAndTextExists(desktopSiteToggle(isRequestDesktopSiteEnabled))
desktopSiteButton().check(matches(isChecked())) }
} else {
desktopSiteButton().check(matches(not(isChecked()))) fun verifyPageThreeDotMainMenuItems(isRequestDesktopSiteEnabled: Boolean) {
} expandMenu()
assertItemContainingTextExists(
normalBrowsingNewTabButton,
bookmarksButton,
historyButton,
downloadsButton,
addOnsButton,
syncAndSaveDataButton,
findInPageButton,
desktopSiteButton,
reportSiteIssueButton,
addToHomeScreenButton,
addToShortcutsButton,
saveToCollectionButton,
settingsButton(),
)
assertCheckedItemWithResIdAndTextExists(addBookmarkButton)
assertCheckedItemWithResIdAndTextExists(desktopSiteToggle(isRequestDesktopSiteEnabled))
assertItemWithDescriptionExists(
backButton,
forwardButton,
shareButton,
refreshButton,
)
} }
fun verifyPageThreeDotMainMenuItems() { fun verifyHomeThreeDotMainMenuItems(isRequestDesktopSiteEnabled: Boolean) {
verifyNewTabButton() assertItemContainingTextExists(
verifyBookmarksButton() bookmarksButton,
verifyAddBookmarkButton() historyButton,
verifyHistoryButton() downloadsButton,
verifyDownloadsButton() addOnsButton,
verifyAddOnsButton() // Disabled step due to https://github.com/mozilla-mobile/fenix/issues/26788
verifySyncSignInButton() // syncAndSaveDataButton,
threeDotMenuRecyclerView().perform(swipeUp()) desktopSiteButton,
verifyFindInPageButton() whatsNewButton,
verifyDesktopSite() helpButton,
threeDotMenuRecyclerView().perform(swipeUp()) customizeHomeButton,
verifyReportSiteIssueButton() settingsButton(),
verifyAddToTopSitesButton() )
verifyAddToMobileHome()
verifySaveCollection() assertCheckedItemWithResIdAndTextExists(desktopSiteToggle(isRequestDesktopSiteEnabled))
verifySettingsButton()
verifyShareButton()
verifyForwardButton()
verifyRefreshButton()
} }
private fun assertShareTabsOverlay() { private fun assertShareTabsOverlay() {
@ -164,7 +174,7 @@ class ThreeDotMenuMainRobot {
fun openDownloadsManager(interact: DownloadRobot.() -> Unit): DownloadRobot.Transition { fun openDownloadsManager(interact: DownloadRobot.() -> Unit): DownloadRobot.Transition {
threeDotMenuRecyclerView().perform(swipeDown()) threeDotMenuRecyclerView().perform(swipeDown())
downloadsButton().click() downloadsButton.click()
DownloadRobot().interact() DownloadRobot().interact()
return DownloadRobot.Transition() return DownloadRobot.Transition()
@ -173,7 +183,7 @@ class ThreeDotMenuMainRobot {
fun openSyncSignIn(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition { fun openSyncSignIn(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
threeDotMenuRecyclerView().perform(swipeDown()) threeDotMenuRecyclerView().perform(swipeDown())
mDevice.waitNotNull(Until.findObject(By.text("Sync and save data")), waitingTime) mDevice.waitNotNull(Until.findObject(By.text("Sync and save data")), waitingTime)
syncSignInButton().click() syncAndSaveDataButton.click()
SyncSignInRobot().interact() SyncSignInRobot().interact()
return SyncSignInRobot.Transition() return SyncSignInRobot.Transition()
@ -183,7 +193,7 @@ class ThreeDotMenuMainRobot {
threeDotMenuRecyclerView().perform(swipeDown()) threeDotMenuRecyclerView().perform(swipeDown())
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime) mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
bookmarksButton().click() bookmarksButton.click()
assertTrue(mDevice.findObject(UiSelector().resourceId("$packageName:id/bookmark_list")).waitForExists(waitingTime)) assertTrue(mDevice.findObject(UiSelector().resourceId("$packageName:id/bookmark_list")).waitForExists(waitingTime))
BookmarksRobot().interact() BookmarksRobot().interact()
@ -193,7 +203,7 @@ class ThreeDotMenuMainRobot {
fun openHistory(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition { fun openHistory(interact: HistoryRobot.() -> Unit): HistoryRobot.Transition {
threeDotMenuRecyclerView().perform(swipeDown()) threeDotMenuRecyclerView().perform(swipeDown())
mDevice.waitNotNull(Until.findObject(By.text("History")), waitingTime) mDevice.waitNotNull(Until.findObject(By.text("History")), waitingTime)
historyButton().click() historyButton.click()
HistoryRobot().interact() HistoryRobot().interact()
return HistoryRobot.Transition() return HistoryRobot.Transition()
@ -201,7 +211,7 @@ class ThreeDotMenuMainRobot {
fun bookmarkPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun bookmarkPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime) mDevice.waitNotNull(Until.findObject(By.text("Bookmarks")), waitingTime)
addBookmarkButton().click() addBookmarkButton.click()
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
@ -217,7 +227,7 @@ class ThreeDotMenuMainRobot {
fun openHelp(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun openHelp(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.text("Help")), waitingTime) mDevice.waitNotNull(Until.findObject(By.text("Help")), waitingTime)
helpButton().click() helpButton.click()
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
@ -232,7 +242,7 @@ class ThreeDotMenuMainRobot {
waitingTime, waitingTime,
) )
customizeHomeButton().click() customizeHomeButton.click()
mDevice.findObject( mDevice.findObject(
UiSelector().resourceId("$packageName:id/recycler_view"), UiSelector().resourceId("$packageName:id/recycler_view"),
@ -243,35 +253,27 @@ class ThreeDotMenuMainRobot {
} }
fun goForward(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun goForward(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
forwardButton().click() forwardButton.click()
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
} }
fun goBack(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun goToPreviousPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
backButton().click() backButton.click()
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
} }
fun clickShareButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition { fun clickShareButton(interact: ShareOverlayRobot.() -> Unit): ShareOverlayRobot.Transition {
shareButton().click() shareButton.click()
mDevice.waitNotNull(Until.findObject(By.text("ALL ACTIONS")), waitingTime) mDevice.waitNotNull(Until.findObject(By.text("ALL ACTIONS")), waitingTime)
ShareOverlayRobot().interact() ShareOverlayRobot().interact()
return ShareOverlayRobot.Transition() return ShareOverlayRobot.Transition()
} }
fun close(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
// Close three dot
mDevice.pressBack()
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun closeBrowserMenuToBrowser(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun closeBrowserMenuToBrowser(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
// Close three dot // Close three dot
mDevice.pressBack() mDevice.pressBack()
@ -281,8 +283,7 @@ class ThreeDotMenuMainRobot {
} }
fun refreshPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun refreshPage(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
assertRefreshButton() refreshButton.click()
refreshButton().click()
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
@ -298,7 +299,7 @@ class ThreeDotMenuMainRobot {
fun openReportSiteIssue(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun openReportSiteIssue(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
threeDotMenuRecyclerView().perform(swipeUp()) threeDotMenuRecyclerView().perform(swipeUp())
threeDotMenuRecyclerView().perform(swipeUp()) threeDotMenuRecyclerView().perform(swipeUp())
reportSiteIssueButton().click() reportSiteIssueButton.click()
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
@ -308,7 +309,7 @@ class ThreeDotMenuMainRobot {
threeDotMenuRecyclerView().perform(swipeUp()) threeDotMenuRecyclerView().perform(swipeUp())
threeDotMenuRecyclerView().perform(swipeUp()) threeDotMenuRecyclerView().perform(swipeUp())
mDevice.waitNotNull(Until.findObject(By.text("Find in page")), waitingTime) mDevice.waitNotNull(Until.findObject(By.text("Find in page")), waitingTime)
findInPageButton().click() findInPageButton.click()
FindInPageRobot().interact() FindInPageRobot().interact()
return FindInPageRobot.Transition() return FindInPageRobot.Transition()
@ -316,7 +317,7 @@ class ThreeDotMenuMainRobot {
fun openWhatsNew(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun openWhatsNew(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.text("Whats new")), waitingTime) mDevice.waitNotNull(Until.findObject(By.text("Whats new")), waitingTime)
whatsNewButton().click() whatsNewButton.click()
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
@ -332,7 +333,26 @@ class ThreeDotMenuMainRobot {
} }
fun addToFirefoxHome(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun addToFirefoxHome(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
addToTopSitesButton().click() for (i in 1..RETRY_COUNT) {
try {
addToShortcutsButton.also {
it.waitForExists(waitingTime)
it.click()
}
break
} catch (e: UiObjectNotFoundException) {
if (i == RETRY_COUNT) {
throw e
} else {
mDevice.pressBack()
navigationToolbar {
}.openThreeDotMenu {
expandMenu()
}
}
}
}
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
@ -346,8 +366,7 @@ class ThreeDotMenuMainRobot {
} }
fun openAddToHomeScreen(interact: AddToHomeScreenRobot.() -> Unit): AddToHomeScreenRobot.Transition { fun openAddToHomeScreen(interact: AddToHomeScreenRobot.() -> Unit): AddToHomeScreenRobot.Transition {
mDevice.waitNotNull(Until.findObject(By.text("Add to Home screen")), waitingTime) addToHomeScreenButton.clickAndWaitForNewWindow(waitingTime)
addToHomeScreenButton().click()
AddToHomeScreenRobot().interact() AddToHomeScreenRobot().interact()
return AddToHomeScreenRobot.Transition() return AddToHomeScreenRobot.Transition()
@ -368,7 +387,7 @@ class ThreeDotMenuMainRobot {
threeDotMenuRecyclerView().perform(swipeUp()) threeDotMenuRecyclerView().perform(swipeUp())
mDevice.waitNotNull(Until.findObject(By.text("Save to collection")), waitingTime) mDevice.waitNotNull(Until.findObject(By.text("Save to collection")), waitingTime)
saveCollectionButton().click() saveToCollectionButton.click()
CollectionRobot().interact() CollectionRobot().interact()
return CollectionRobot.Transition() return CollectionRobot.Transition()
} }
@ -396,7 +415,7 @@ class ThreeDotMenuMainRobot {
fun switchDesktopSiteMode(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition { fun switchDesktopSiteMode(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
threeDotMenuRecyclerView().perform(swipeUp()) threeDotMenuRecyclerView().perform(swipeUp())
threeDotMenuRecyclerView().perform(swipeUp()) threeDotMenuRecyclerView().perform(swipeUp())
desktopSiteButton().click() desktopSiteButton.click()
BrowserRobot().interact() BrowserRobot().interact()
return BrowserRobot.Transition() return BrowserRobot.Transition()
@ -417,61 +436,10 @@ private fun threeDotMenuRecyclerViewExists() {
threeDotMenuRecyclerView().check(matches(isDisplayed())) threeDotMenuRecyclerView().check(matches(isDisplayed()))
} }
private fun settingsButton(localizedText: String = getStringResource(R.string.browser_menu_settings)) =
mDevice.findObject(UiSelector().text(localizedText))
private fun assertSettingsButton() = assertTrue(settingsButton().waitForExists(waitingTime))
private fun customizeHomeButton() =
onView(
allOf(
withId(R.id.text),
withText(R.string.browser_menu_customize_home_1),
),
)
private fun assertCustomizeHomeButton() =
customizeHomeButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun addOnsButton() = onView(allOf(withText("Add-ons")))
private fun assertAddOnsButton() {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown())
addOnsButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun historyButton() = onView(allOf(withText(R.string.library_history)))
private fun assertHistoryButton() = historyButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun bookmarksButton() = onView(allOf(withText(R.string.library_bookmarks)))
private fun assertBookmarksButton() = bookmarksButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun syncSignInButton() = onView(withText("Sync and save data"))
private fun assertSyncSignInButton() = syncSignInButton().check(matches(isDisplayed()))
private fun helpButton() = onView(allOf(withText(R.string.browser_menu_help)))
private fun assertHelpButton() = helpButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun forwardButton() = mDevice.findObject(UiSelector().description("Forward"))
private fun assertForwardButton() = assertTrue(forwardButton().waitForExists(waitingTime))
private fun backButton() = mDevice.findObject(UiSelector().description("Back"))
private fun addBookmarkButton() = onView(allOf(withId(R.id.checkbox), withText("Add")))
private fun assertAddBookmarkButton() {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeUp())
addBookmarkButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun editBookmarkButton() = onView(withText("Edit")) private fun editBookmarkButton() = onView(withText("Edit"))
private fun assertEditBookmarkButton() = editBookmarkButton() private fun assertEditBookmarkButton() = editBookmarkButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun refreshButton() = mDevice.findObject(UiSelector().description("Refresh"))
private fun assertRefreshButton() = assertTrue(refreshButton().waitForExists(waitingTime))
private fun stopLoadingButton() = onView(ViewMatchers.withContentDescription("Stop")) private fun stopLoadingButton() = onView(ViewMatchers.withContentDescription("Stop"))
private fun closeAllTabsButton() = onView(allOf(withText("Close all tabs"))).inRoot(RootMatchers.isPlatformPopup()) private fun closeAllTabsButton() = onView(allOf(withText("Close all tabs"))).inRoot(RootMatchers.isPlatformPopup())
@ -482,36 +450,10 @@ private fun shareTabButton() = onView(allOf(withText("Share all tabs"))).inRoot(
private fun assertShareTabButton() = shareTabButton() private fun assertShareTabButton() = shareTabButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun shareButton() = mDevice.findObject(UiSelector().description("Share"))
private fun assertShareButton() = assertTrue(shareButton().waitForExists(waitingTime))
private fun saveCollectionButton() = onView(allOf(withText("Save to collection"))).inRoot(RootMatchers.isPlatformPopup())
private fun assertSaveCollectionButton() = saveCollectionButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun selectTabsButton() = onView(allOf(withText("Select tabs"))).inRoot(RootMatchers.isPlatformPopup()) private fun selectTabsButton() = onView(allOf(withText("Select tabs"))).inRoot(RootMatchers.isPlatformPopup())
private fun assertSelectTabsButton() = selectTabsButton() private fun assertSelectTabsButton() = selectTabsButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun reportSiteIssueButton() = onView(withText("Report Site Issue…"))
private fun assertReportSiteIssueButton() = reportSiteIssueButton().check(matches(isDisplayed()))
private fun findInPageButton() = onView(allOf(withText("Find in page")))
private fun assertFindInPageButton() = findInPageButton()
private fun whatsNewButton() = onView(
allOf(
withText("Whats new"),
withEffectiveVisibility(Visibility.VISIBLE),
),
)
private fun assertWhatsNewButton() = whatsNewButton()
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun addToHomeScreenButton() = onView(withText("Add to Home screen"))
private fun readerViewAppearanceToggle() = private fun readerViewAppearanceToggle() =
mDevice.findObject(UiSelector().text("Customize reader view")) mDevice.findObject(UiSelector().text("Customize reader view"))
@ -532,21 +474,9 @@ private fun assertReaderViewAppearanceButton(visible: Boolean) {
} }
} }
private fun addToTopSitesButton() =
onView(allOf(withText(R.string.browser_menu_add_to_shortcuts)))
private fun removeFromShortcutsButton() = private fun removeFromShortcutsButton() =
onView(allOf(withText(R.string.browser_menu_remove_from_shortcuts))) onView(allOf(withText(R.string.browser_menu_remove_from_shortcuts)))
private fun assertAddToTopSitesButton() {
onView(withId(R.id.mozac_browser_menu_recyclerView))
.perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(R.string.browser_menu_add_to_shortcuts)),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertRemoveFromShortcutsButton() { private fun assertRemoveFromShortcutsButton() {
onView(withId(R.id.mozac_browser_menu_recyclerView)) onView(withId(R.id.mozac_browser_menu_recyclerView))
.perform( .perform(
@ -556,26 +486,8 @@ private fun assertRemoveFromShortcutsButton() {
).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) ).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
} }
private fun addToMobileHomeButton() =
onView(allOf(withText(R.string.browser_menu_add_to_homescreen)))
private fun assertAddToMobileHome() {
onView(withId(R.id.mozac_browser_menu_recyclerView))
.perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(R.string.browser_menu_add_to_homescreen)),
),
).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun installPWAButton() = mDevice.findObject(UiSelector().text("Install")) private fun installPWAButton() = mDevice.findObject(UiSelector().text("Install"))
private fun desktopSiteButton() = onView(withId(R.id.switch_widget))
private fun assertDesktopSite() {
threeDotMenuRecyclerView().perform(swipeUp())
desktopSiteButton().check(matches(isDisplayed()))
}
private fun openInAppButton() = private fun openInAppButton() =
onView( onView(
allOf( allOf(
@ -584,15 +496,9 @@ private fun openInAppButton() =
), ),
) )
private fun downloadsButton() = onView(withText(R.string.library_downloads))
private fun assertDownloadsButton() {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown())
downloadsButton().check(matches(isDisplayed()))
}
private fun clickAddonsManagerButton() { private fun clickAddonsManagerButton() {
onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown()) onView(withId(R.id.mozac_browser_menu_menuView)).perform(swipeDown())
addOnsButton().check(matches(isCompletelyDisplayed())).click() addOnsButton.click()
} }
private fun shareAllTabsButton() = private fun shareAllTabsButton() =
@ -605,4 +511,45 @@ private fun assertShareAllTabsButton() {
) )
} }
private fun assertNormalBrowsingNewTabButton() = onView(withText("New tab")).check(matches(isDisplayed())) private val bookmarksButton =
itemContainingText(getStringResource(R.string.library_bookmarks))
private val historyButton =
itemContainingText(getStringResource(R.string.library_history))
private val downloadsButton =
itemContainingText(getStringResource(R.string.library_downloads))
private val addOnsButton =
itemContainingText(getStringResource(R.string.browser_menu_add_ons))
private val desktopSiteButton =
itemContainingText(getStringResource(R.string.browser_menu_desktop_site))
private fun desktopSiteToggle(state: Boolean) =
checkedItemWithResIdAndText(
"$packageName:id/switch_widget",
getStringResource(R.string.browser_menu_desktop_site),
state,
)
private val whatsNewButton =
itemContainingText(getStringResource(R.string.browser_menu_whats_new))
private val helpButton =
itemContainingText(getStringResource(R.string.browser_menu_help))
private val customizeHomeButton =
itemContainingText(getStringResource(R.string.browser_menu_customize_home_1))
private fun settingsButton(localizedText: String = getStringResource(R.string.browser_menu_settings)) =
itemContainingText(localizedText)
private val syncAndSaveDataButton =
itemContainingText(getStringResource(R.string.sync_menu_sync_and_save_data))
private val normalBrowsingNewTabButton =
itemContainingText(getStringResource(R.string.library_new_tab))
private val addBookmarkButton =
itemWithResIdAndText(
"$packageName:id/checkbox",
getStringResource(R.string.browser_menu_add),
)
private val findInPageButton = itemContainingText(getStringResource(R.string.browser_menu_find_in_page))
private val reportSiteIssueButton = itemContainingText("Report Site Issue")
private val addToHomeScreenButton = itemContainingText(getStringResource(R.string.browser_menu_add_to_homescreen))
private val addToShortcutsButton = itemContainingText(getStringResource(R.string.browser_menu_add_to_shortcuts))
private val saveToCollectionButton = itemContainingText(getStringResource(R.string.browser_menu_save_to_collection_2))
private val backButton = itemWithDescription(getStringResource(R.string.browser_menu_back))
private val forwardButton = itemWithDescription(getStringResource(R.string.browser_menu_forward))
private val shareButton = itemWithDescription(getStringResource(R.string.share_button_content_description))
private val refreshButton = itemWithDescription(getStringResource(R.string.browser_menu_refresh))

@ -28,6 +28,7 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromAbout(R.id.aboutFragment), FromAbout(R.id.aboutFragment),
FromTrackingProtection(R.id.trackingProtectionFragment), FromTrackingProtection(R.id.trackingProtectionFragment),
FromHttpsOnlyMode(R.id.httpsOnlyFragment), FromHttpsOnlyMode(R.id.httpsOnlyFragment),
FromCookieBanner(R.id.cookieBannerFragment),
FromTrackingProtectionDialog(R.id.trackingProtectionPanelDialogFragment), FromTrackingProtectionDialog(R.id.trackingProtectionPanelDialogFragment),
FromSavedLoginsFragment(R.id.savedLoginsFragment), FromSavedLoginsFragment(R.id.savedLoginsFragment),
FromAddNewDeviceFragment(R.id.addNewDeviceFragment), FromAddNewDeviceFragment(R.id.addNewDeviceFragment),

@ -67,10 +67,15 @@ object FeatureFlags {
*/ */
const val saveToPDF = true const val saveToPDF = true
/**
* Enables the notification pre permission prompt.
*/
const val notificationPrePermissionPromptEnabled = true
/** /**
* Enables storage maintenance feature. * Enables storage maintenance feature.
* *
* Feature flag tracking: https://github.com/mozilla-mobile/fenix/issues/27759 * Feature flag tracking: https://github.com/mozilla-mobile/fenix/issues/27759
* */ * */
val storageMaintenanceFeature = Config.channel.isNightlyOrDebug val storageMaintenanceFeature = Config.channel.isNightlyOrDebug || Config.channel.isBeta
} }

@ -61,6 +61,7 @@ import mozilla.components.support.webextensions.WebExtensionSupport
import org.mozilla.fenix.GleanMetrics.Addons import org.mozilla.fenix.GleanMetrics.Addons
import org.mozilla.fenix.GleanMetrics.AndroidAutofill import org.mozilla.fenix.GleanMetrics.AndroidAutofill
import org.mozilla.fenix.GleanMetrics.CustomizeHome import org.mozilla.fenix.GleanMetrics.CustomizeHome
import org.mozilla.fenix.GleanMetrics.Events.marketingNotificationAllowed
import org.mozilla.fenix.GleanMetrics.GleanBuildInfo import org.mozilla.fenix.GleanMetrics.GleanBuildInfo
import org.mozilla.fenix.GleanMetrics.Metrics import org.mozilla.fenix.GleanMetrics.Metrics
import org.mozilla.fenix.GleanMetrics.PerfStartup import org.mozilla.fenix.GleanMetrics.PerfStartup
@ -73,14 +74,16 @@ import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.metrics.MetricServiceType import org.mozilla.fenix.components.metrics.MetricServiceType
import org.mozilla.fenix.components.metrics.MozillaProductDetector import org.mozilla.fenix.components.metrics.MozillaProductDetector
import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.areNotificationsEnabledSafe
import org.mozilla.fenix.ext.containsQueryParameters import org.mozilla.fenix.ext.containsQueryParameters
import org.mozilla.fenix.ext.getCustomGleanServerUrlIfAvailable import org.mozilla.fenix.ext.getCustomGleanServerUrlIfAvailable
import org.mozilla.fenix.ext.isCustomEngine import org.mozilla.fenix.ext.isCustomEngine
import org.mozilla.fenix.ext.isKnownSearchDomain import org.mozilla.fenix.ext.isKnownSearchDomain
import org.mozilla.fenix.ext.isNotificationChannelEnabled
import org.mozilla.fenix.ext.setCustomEndpointIfAvailable import org.mozilla.fenix.ext.setCustomEndpointIfAvailable
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.onboarding.ensureMarketingChannelExists import org.mozilla.fenix.onboarding.MARKETING_CHANNEL_ID
import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
import org.mozilla.fenix.perf.ProfilerMarkerFactProcessor import org.mozilla.fenix.perf.ProfilerMarkerFactProcessor
import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.perf.StartupTimeline
@ -203,6 +206,11 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
ProfilerMarkerFactProcessor.create { components.core.engine.profiler }.register() ProfilerMarkerFactProcessor.create { components.core.engine.profiler }.register()
run { run {
// Make sure the engine is initialized and ready to use.
components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
components.core.engine.warmUp()
}
// We need to always initialize Glean and do it early here. // We need to always initialize Glean and do it early here.
initializeGlean() initializeGlean()
@ -213,10 +221,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
components.strictMode.enableStrictMode(true) components.strictMode.enableStrictMode(true)
warmBrowsersCache() warmBrowsersCache()
// Make sure the engine is initialized and ready to use.
components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) {
components.core.engine.warmUp()
}
initializeWebExtensionSupport() initializeWebExtensionSupport()
if (FeatureFlags.storageMaintenanceFeature) { if (FeatureFlags.storageMaintenanceFeature) {
// Make sure to call this function before registering a storage worker // Make sure to call this function before registering a storage worker
@ -252,6 +256,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
ProcessLifecycleOwner.get().lifecycle.addObserver(TelemetryLifecycleObserver(components.core.store)) ProcessLifecycleOwner.get().lifecycle.addObserver(TelemetryLifecycleObserver(components.core.store))
components.analytics.metricsStorage.tryRegisterAsUsageRecorder(this)
downloadWallpapers() downloadWallpapers()
} }
@ -368,16 +374,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
} }
} }
// For Android 13 or above, prompt the user for notification permission at the start.
// Regardless if the user accepts or denies the permission prompt, the prompt will occur only once.
fun queueNotificationPermissionRequest() {
if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
queue.runIfReadyOrQueue {
ensureMarketingChannelExists(this)
}
}
}
initQueue() initQueue()
// We init these items in the visual completeness queue to avoid them initing in the critical // We init these items in the visual completeness queue to avoid them initing in the critical
@ -387,7 +383,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
queueReviewPrompt() queueReviewPrompt()
queueRestoreLocale() queueRestoreLocale()
queueStorageMaintenance() queueStorageMaintenance()
queueNotificationPermissionRequest()
} }
private fun startMetricsIfEnabled() { private fun startMetricsIfEnabled() {
@ -749,14 +744,11 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
defaultWallpaper.set(isDefaultTheCurrentWallpaper) defaultWallpaper.set(isDefaultTheCurrentWallpaper)
@Suppress("TooGenericExceptionCaught") val notificationManagerCompat = NotificationManagerCompat.from(applicationContext)
try { notificationsAllowed.set(notificationManagerCompat.areNotificationsEnabledSafe())
notificationsAllowed.set( marketingNotificationAllowed.set(
NotificationManagerCompat.from(applicationContext).areNotificationsEnabled(), notificationManagerCompat.isNotificationChannelEnabled(MARKETING_CHANNEL_ID),
) )
} catch (e: Exception) {
Logger.warn("Failed to check if notifications are enabled", e)
}
} }
with(AndroidAutofill) { with(AndroidAutofill) {

@ -28,6 +28,7 @@ import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PROTECTED import androidx.annotation.VisibleForTesting.Companion.PROTECTED
import androidx.appcompat.app.ActionBar import androidx.appcompat.app.ActionBar
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
@ -67,6 +68,7 @@ import mozilla.components.support.ktx.android.content.share
import mozilla.components.support.ktx.kotlin.isUrl import mozilla.components.support.ktx.kotlin.isUrl
import mozilla.components.support.ktx.kotlin.toNormalizedUrl import mozilla.components.support.ktx.kotlin.toNormalizedUrl
import mozilla.components.support.locale.LocaleAwareAppCompatActivity import mozilla.components.support.locale.LocaleAwareAppCompatActivity
import mozilla.components.support.utils.ManufacturerCodes
import mozilla.components.support.utils.SafeIntent import mozilla.components.support.utils.SafeIntent
import mozilla.components.support.utils.toSafeIntent import mozilla.components.support.utils.toSafeIntent
import mozilla.components.support.webextensions.WebExtensionPopupFeature import mozilla.components.support.webextensions.WebExtensionPopupFeature
@ -84,12 +86,14 @@ import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
import org.mozilla.fenix.databinding.ActivityHomeBinding import org.mozilla.fenix.databinding.ActivityHomeBinding
import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragmentDirections import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragmentDirections
import org.mozilla.fenix.ext.alreadyOnDestination import org.mozilla.fenix.ext.alreadyOnDestination
import org.mozilla.fenix.ext.areNotificationsEnabledSafe
import org.mozilla.fenix.ext.breadcrumb import org.mozilla.fenix.ext.breadcrumb
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.setNavigationIcon import org.mozilla.fenix.ext.setNavigationIcon
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.HomeFragmentDirections import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.home.intent.AssistIntentProcessor
import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor
import org.mozilla.fenix.home.intent.DefaultBrowserIntentProcessor import org.mozilla.fenix.home.intent.DefaultBrowserIntentProcessor
import org.mozilla.fenix.home.intent.HomeDeepLinkIntentProcessor import org.mozilla.fenix.home.intent.HomeDeepLinkIntentProcessor
@ -102,9 +106,11 @@ import org.mozilla.fenix.library.bookmarks.DesktopFolders
import org.mozilla.fenix.library.history.HistoryFragmentDirections import org.mozilla.fenix.library.history.HistoryFragmentDirections
import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentDirections import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentDirections
import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.onboarding.DefaultBrowserNotificationWorker import org.mozilla.fenix.onboarding.DefaultBrowserNotificationWorker
import org.mozilla.fenix.onboarding.FenixOnboarding import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.onboarding.ReEngagementNotificationWorker import org.mozilla.fenix.onboarding.ReEngagementNotificationWorker
import org.mozilla.fenix.onboarding.ensureMarketingChannelExists
import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
import org.mozilla.fenix.perf.Performance import org.mozilla.fenix.perf.Performance
@ -115,6 +121,7 @@ import org.mozilla.fenix.perf.StartupTimeline
import org.mozilla.fenix.perf.StartupTypeTelemetry import org.mozilla.fenix.perf.StartupTypeTelemetry
import org.mozilla.fenix.search.SearchDialogFragmentDirections import org.mozilla.fenix.search.SearchDialogFragmentDirections
import org.mozilla.fenix.session.PrivateNotificationService import org.mozilla.fenix.session.PrivateNotificationService
import org.mozilla.fenix.settings.CookieBannersFragmentDirections
import org.mozilla.fenix.settings.HttpsOnlyFragmentDirections import org.mozilla.fenix.settings.HttpsOnlyFragmentDirections
import org.mozilla.fenix.settings.SettingsFragmentDirections import org.mozilla.fenix.settings.SettingsFragmentDirections
import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections
@ -132,7 +139,6 @@ import org.mozilla.fenix.theme.DefaultThemeManager
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.trackingprotection.TrackingProtectionPanelDialogFragmentDirections import org.mozilla.fenix.trackingprotection.TrackingProtectionPanelDialogFragmentDirections
import org.mozilla.fenix.utils.BrowsersCache import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.ManufacturerCodes
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -182,6 +188,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
listOf( listOf(
HomeDeepLinkIntentProcessor(this), HomeDeepLinkIntentProcessor(this),
SpeechProcessingIntentProcessor(this, components.core.store), SpeechProcessingIntentProcessor(this, components.core.store),
AssistIntentProcessor(),
StartSearchIntentProcessor(), StartSearchIntentProcessor(),
OpenBrowserIntentProcessor(this, ::getIntentSessionId), OpenBrowserIntentProcessor(this, ::getIntentSessionId),
OpenSpecificTabIntentProcessor(this), OpenSpecificTabIntentProcessor(this),
@ -317,6 +324,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
} }
} }
showNotificationPermissionPromptIfRequired()
components.backgroundServices.accountManagerAvailableQueue.runIfReadyOrQueue { components.backgroundServices.accountManagerAvailableQueue.runIfReadyOrQueue {
lifecycleScope.launch(IO) { lifecycleScope.launch(IO) {
// If we're authenticated, kick-off a sync and a device state refresh. // If we're authenticated, kick-off a sync and a device state refresh.
@ -334,6 +343,30 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE. StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE.
} }
/**
* On Android 13 or above, prompt the user for notification permission at the start.
* Show the pre permission dialog to the user once if the notification are not enabled.
*/
private fun showNotificationPermissionPromptIfRequired() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
!NotificationManagerCompat.from(applicationContext).areNotificationsEnabledSafe() &&
settings().numberOfAppLaunches <= 1
) {
// Recording the exposure event here to capture all users who met all criteria to receive
// the pre permission notification prompt
FxNimbus.features.prePermissionNotificationPrompt.recordExposure()
if (settings().notificationPrePermissionPromptEnabled) {
if (!settings().isNotificationPrePermissionShown) {
navHost.navController.navigate(NavGraphDirections.actionGlobalHomeNotificationPermissionDialog())
}
} else {
// This will trigger the notification permission system dialog as app targets sdk 32.
ensureMarketingChannelExists(applicationContext)
}
}
}
private fun checkAndExitPiP() { private fun checkAndExitPiP() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInPictureInPictureMode && intent != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInPictureInPictureMode && intent != null) {
// Exit PiP mode // Exit PiP mode
@ -848,6 +881,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
HistoryMetadataGroupFragmentDirections.actionGlobalBrowser(customTabSessionId) HistoryMetadataGroupFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromTrackingProtectionExceptions -> BrowserDirection.FromTrackingProtectionExceptions ->
TrackingProtectionExceptionsFragmentDirections.actionGlobalBrowser(customTabSessionId) TrackingProtectionExceptionsFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromCookieBanner ->
CookieBannersFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromHttpsOnlyMode -> BrowserDirection.FromHttpsOnlyMode ->
HttpsOnlyFragmentDirections.actionGlobalBrowser(customTabSessionId) HttpsOnlyFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromAbout -> BrowserDirection.FromAbout ->

@ -243,7 +243,12 @@ class InstalledAddonDetailsFragment : Fragment() {
val shouldCreatePrivateSession = val shouldCreatePrivateSession =
(activity as HomeActivity).browsingModeManager.mode.isPrivate (activity as HomeActivity).browsingModeManager.mode.isPrivate
components.useCases.tabsUseCases.addTab(settingUrl, private = shouldCreatePrivateSession) // If the addon settings page is already open in a tab, select that one
components.useCases.tabsUseCases.selectOrAddTab(
url = settingUrl,
private = shouldCreatePrivateSession,
ignoreFragment = true,
)
InstalledAddonDetailsFragmentDirections.actionGlobalBrowser(null) InstalledAddonDetailsFragmentDirections.actionGlobalBrowser(null)
} else { } else {

@ -15,11 +15,14 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.thumbnails.BrowserThumbnails import mozilla.components.browser.thumbnails.BrowserThumbnails
import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.concept.engine.permission.SitePermissions import mozilla.components.concept.engine.permission.SitePermissions
@ -28,9 +31,11 @@ import mozilla.components.feature.contextmenu.ContextMenuCandidate
import mozilla.components.feature.readerview.ReaderViewFeature import mozilla.components.feature.readerview.ReaderViewFeature
import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tabs.WindowFeature import mozilla.components.feature.tabs.WindowFeature
import mozilla.components.lib.state.ext.consumeFlow
import mozilla.components.service.glean.private.NoExtras import mozilla.components.service.glean.private.NoExtras
import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.GleanMetrics.ReaderMode import org.mozilla.fenix.GleanMetrics.ReaderMode
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
@ -43,6 +48,7 @@ import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.runIfFragmentIsAttached import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.dialog.CookieBannerReEngagementDialogUtils
import org.mozilla.fenix.shortcut.PwaOnboardingObserver import org.mozilla.fenix.shortcut.PwaOnboardingObserver
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
@ -173,6 +179,9 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
view = view, view = view,
) )
} }
if (!context.settings().shouldUseCookieBanner && !context.settings().userOptOutOfReEngageCookieBannerDialog) {
observeCookieBannerHandlingState(context.components.core.store)
}
} }
override fun onUpdateToolbarForConfigurationChange(toolbar: BrowserToolbarView) { override fun onUpdateToolbarForConfigurationChange(toolbar: BrowserToolbarView) {
@ -370,12 +379,17 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
useCase.containsException(tab.id) { hasTrackingProtectionException -> useCase.containsException(tab.id) { hasTrackingProtectionException ->
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
val cookieBannersStorage = requireComponents.core.cookieBannersStorage val cookieBannersStorage = requireComponents.core.cookieBannersStorage
val hasCookieBannerException = withContext(Dispatchers.IO) { val hasCookieBannerException =
cookieBannersStorage.hasException( if (requireContext().settings().shouldUseCookieBanner) {
tab.content.url, withContext(Dispatchers.IO) {
tab.content.private, cookieBannersStorage.hasException(
) tab.content.url,
} tab.content.private,
)
}
} else {
false
}
runIfFragmentIsAttached { runIfFragmentIsAttached {
val isTrackingProtectionEnabled = val isTrackingProtectionEnabled =
tab.trackingProtection.enabled && !hasTrackingProtectionException tab.trackingProtection.enabled && !hasTrackingProtectionException
@ -476,4 +490,22 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
internal fun updateLastBrowseActivity() { internal fun updateLastBrowseActivity() {
requireContext().settings().lastBrowseActivity = System.currentTimeMillis() requireContext().settings().lastBrowseActivity = System.currentTimeMillis()
} }
private fun observeCookieBannerHandlingState(store: BrowserStore) {
consumeFlow(store) { flow ->
flow.mapNotNull { state ->
state.findCustomTabOrSelectedTab(customTabSessionId)
}.ifAnyChanged { tab ->
arrayOf(
tab.cookieBanner,
)
}.collect {
CookieBannerReEngagementDialogUtils.tryToShowReEngagementDialog(
settings = requireContext().settings(),
status = it.cookieBanner,
navController = findNavController(),
)
}
}
}
} }

@ -22,8 +22,10 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ReleaseChannel import org.mozilla.fenix.ReleaseChannel
import org.mozilla.fenix.components.metrics.AdjustMetricsService import org.mozilla.fenix.components.metrics.AdjustMetricsService
import org.mozilla.fenix.components.metrics.DefaultMetricsStorage
import org.mozilla.fenix.components.metrics.GleanMetricsService import org.mozilla.fenix.components.metrics.GleanMetricsService
import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.components.metrics.MetricsStorage
import org.mozilla.fenix.experiments.createNimbus import org.mozilla.fenix.experiments.createNimbus
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.gleanplumb.CustomAttributeProvider import org.mozilla.fenix.gleanplumb.CustomAttributeProvider
@ -31,6 +33,7 @@ import org.mozilla.fenix.gleanplumb.NimbusMessagingStorage
import org.mozilla.fenix.gleanplumb.OnDiskMessageMetadataStorage import org.mozilla.fenix.gleanplumb.OnDiskMessageMetadataStorage
import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.perf.lazyMonitored import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID import org.mozilla.geckoview.BuildConfig.MOZ_APP_BUILDID
import org.mozilla.geckoview.BuildConfig.MOZ_APP_VENDOR import org.mozilla.geckoview.BuildConfig.MOZ_APP_VENDOR
import org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION import org.mozilla.geckoview.BuildConfig.MOZ_APP_VERSION
@ -115,6 +118,14 @@ class Analytics(
) )
} }
val metricsStorage: MetricsStorage by lazyMonitored {
DefaultMetricsStorage(
context = context,
settings = context.settings(),
checkDefaultBrowser = { BrowsersCache.all(context).isDefaultBrowser },
)
}
val metrics: MetricController by lazyMonitored { val metrics: MetricController by lazyMonitored {
MetricController.create( MetricController.create(
listOf( listOf(

@ -31,5 +31,20 @@ sealed class Event {
* Event recording the first time Firefox is used 3 days in a row in the first week of install. * Event recording the first time Firefox is used 3 days in a row in the first week of install.
*/ */
object FirstWeekSeriesActivity : GrowthData("20ay7u") object FirstWeekSeriesActivity : GrowthData("20ay7u")
/**
* Event recording that usage time has reached a threshold.
*/
object UsageThreshold : GrowthData("m66prt")
/**
* Event recording the first time Firefox has been resumed in a 24 hour period.
*/
object FirstAppOpenForDay : GrowthData("41hl22")
/**
* Event recording the first time a URI is loaded in Firefox in a 24 hour period.
*/
object FirstUriLoadForDay : GrowthData("ja86ek")
} }
} }

@ -6,6 +6,7 @@ package org.mozilla.fenix.components.metrics
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import mozilla.components.browser.menu.facts.BrowserMenuFacts import mozilla.components.browser.menu.facts.BrowserMenuFacts
import mozilla.components.browser.toolbar.facts.ToolbarFacts
import mozilla.components.concept.awesomebar.AwesomeBar import mozilla.components.concept.awesomebar.AwesomeBar
import mozilla.components.feature.autofill.facts.AutofillFacts import mozilla.components.feature.autofill.facts.AutofillFacts
import mozilla.components.feature.awesomebar.facts.AwesomeBarFacts import mozilla.components.feature.awesomebar.facts.AwesomeBarFacts
@ -42,6 +43,7 @@ import org.mozilla.fenix.GleanMetrics.BrowserSearch
import org.mozilla.fenix.GleanMetrics.ContextMenu import org.mozilla.fenix.GleanMetrics.ContextMenu
import org.mozilla.fenix.GleanMetrics.ContextualMenu import org.mozilla.fenix.GleanMetrics.ContextualMenu
import org.mozilla.fenix.GleanMetrics.CreditCards import org.mozilla.fenix.GleanMetrics.CreditCards
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.LoginDialog import org.mozilla.fenix.GleanMetrics.LoginDialog
import org.mozilla.fenix.GleanMetrics.MediaNotification import org.mozilla.fenix.GleanMetrics.MediaNotification
import org.mozilla.fenix.GleanMetrics.MediaState import org.mozilla.fenix.GleanMetrics.MediaState
@ -147,6 +149,9 @@ internal class ReleaseMetricController(
else -> Unit else -> Unit
} }
} }
Component.BROWSER_TOOLBAR to ToolbarFacts.Items.MENU -> {
Events.toolbarMenuVisible.record(NoExtras())
}
Component.FEATURE_CONTEXTMENU to ContextMenuFacts.Items.ITEM -> { Component.FEATURE_CONTEXTMENU to ContextMenuFacts.Items.ITEM -> {
metadata?.get("item")?.let { item -> metadata?.get("item")?.let { item ->
contextMenuAllowList[item]?.let { extraKey -> contextMenuAllowList[item]?.let { extraKey ->

@ -28,6 +28,7 @@ class MetricsMiddleware(
is AppAction.ResumedMetricsAction -> { is AppAction.ResumedMetricsAction -> {
metrics.track(Event.GrowthData.SetAsDefault) metrics.track(Event.GrowthData.SetAsDefault)
metrics.track(Event.GrowthData.FirstWeekSeriesActivity) metrics.track(Event.GrowthData.FirstWeekSeriesActivity)
metrics.track(Event.GrowthData.UsageThreshold)
} }
else -> Unit else -> Unit
} }

@ -4,11 +4,14 @@
package org.mozilla.fenix.components.metrics package org.mozilla.fenix.components.metrics
import android.app.Activity
import android.app.Application
import android.content.Context import android.content.Context
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mozilla.components.support.utils.ext.getPackageInfoCompat import mozilla.components.support.utils.ext.getPackageInfoCompat
import org.mozilla.fenix.android.DefaultActivityLifecycleCallbacks
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -29,6 +32,18 @@ interface MetricsStorage {
* Updates locally-stored state for an [event] that has just been sent. * Updates locally-stored state for an [event] that has just been sent.
*/ */
suspend fun updateSentState(event: Event) suspend fun updateSentState(event: Event)
/**
* Will try to register this as a recorder of app usage based on whether usage recording is still
* needed. It will measure usage by to monitoring lifecycle callbacks from [application]'s
* activities and should update local state using [updateUsageState].
*/
fun tryRegisterAsUsageRecorder(application: Application)
/**
* Update local state with a [usageLength] measurement.
*/
fun updateUsageState(usageLength: Long)
} }
internal class DefaultMetricsStorage( internal class DefaultMetricsStorage(
@ -62,6 +77,20 @@ internal class DefaultMetricsStorage(
Event.GrowthData.SerpAdClicked -> { Event.GrowthData.SerpAdClicked -> {
currentTime.duringFirstMonth() && !settings.adClickGrowthSent currentTime.duringFirstMonth() && !settings.adClickGrowthSent
} }
Event.GrowthData.UsageThreshold -> {
!settings.usageTimeGrowthSent &&
settings.usageTimeGrowthData > usageThresholdMillis
}
Event.GrowthData.FirstAppOpenForDay -> {
currentTime.afterFirstDay() &&
currentTime.duringFirstMonth() &&
settings.resumeGrowthLastSent.hasBeenMoreThanDaySince()
}
Event.GrowthData.FirstUriLoadForDay -> {
currentTime.afterFirstDay() &&
currentTime.duringFirstMonth() &&
settings.uriLoadGrowthLastSent.hasBeenMoreThanDaySince()
}
} }
} }
@ -76,9 +105,29 @@ internal class DefaultMetricsStorage(
Event.GrowthData.SerpAdClicked -> { Event.GrowthData.SerpAdClicked -> {
settings.adClickGrowthSent = true settings.adClickGrowthSent = true
} }
Event.GrowthData.UsageThreshold -> {
settings.usageTimeGrowthSent = true
}
Event.GrowthData.FirstAppOpenForDay -> {
settings.resumeGrowthLastSent = System.currentTimeMillis()
}
Event.GrowthData.FirstUriLoadForDay -> {
settings.uriLoadGrowthLastSent = System.currentTimeMillis()
}
}
}
override fun tryRegisterAsUsageRecorder(application: Application) {
// Currently there is only interest in measuring usage during the first day of install.
if (!settings.usageTimeGrowthSent && System.currentTimeMillis().duringFirstDay()) {
application.registerActivityLifecycleCallbacks(UsageRecorder(this))
} }
} }
override fun updateUsageState(usageLength: Long) {
settings.usageTimeGrowthData += usageLength
}
private fun updateDaysOfUse() { private fun updateDaysOfUse() {
val daysOfUse = settings.firstWeekDaysOfUseGrowthData val daysOfUse = settings.firstWeekDaysOfUseGrowthData
val currentDate = Calendar.getInstance(Locale.US) val currentDate = Calendar.getInstance(Locale.US)
@ -121,6 +170,12 @@ internal class DefaultMetricsStorage(
calendar.timeInMillis = this calendar.timeInMillis = this
} }
private fun Long.hasBeenMoreThanDaySince() = System.currentTimeMillis() - this > dayMillis
private fun Long.afterFirstDay() = this > getInstalledTime() + dayMillis
private fun Long.duringFirstDay() = this < getInstalledTime() + dayMillis
private fun Long.duringFirstWeek() = this < getInstalledTime() + fullWeekMillis private fun Long.duringFirstWeek() = this < getInstalledTime() + fullWeekMillis
private fun Long.duringFirstMonth() = this < getInstalledTime() + shortestMonthMillis private fun Long.duringFirstMonth() = this < getInstalledTime() + shortestMonthMillis
@ -129,6 +184,28 @@ internal class DefaultMetricsStorage(
calendar.add(Calendar.DAY_OF_MONTH, 1) calendar.add(Calendar.DAY_OF_MONTH, 1)
} }
/**
* This will store app usage time to disk, based on Resume and Pause lifecycle events. Currently,
* there is only interest in usage during the first day after install.
*/
internal class UsageRecorder(
private val metricsStorage: MetricsStorage,
) : DefaultActivityLifecycleCallbacks {
private val activityStartTimes: MutableMap<String, Long?> = mutableMapOf()
override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
activityStartTimes[activity.componentName.toString()] = System.currentTimeMillis()
}
override fun onActivityPaused(activity: Activity) {
super.onActivityPaused(activity)
val startTime = activityStartTimes[activity.componentName.toString()] ?: return
val elapsedTimeMillis = System.currentTimeMillis() - startTime
metricsStorage.updateUsageState(elapsedTimeMillis)
}
}
companion object { companion object {
private const val dayMillis: Long = 1000 * 60 * 60 * 24 private const val dayMillis: Long = 1000 * 60 * 60 * 24
private const val shortestMonthMillis: Long = dayMillis * 28 private const val shortestMonthMillis: Long = dayMillis * 28
@ -137,6 +214,9 @@ internal class DefaultMetricsStorage(
// of the 7th day after install // of the 7th day after install
private const val fullWeekMillis: Long = dayMillis * 8 private const val fullWeekMillis: Long = dayMillis * 8
// The usage threshold we are interested in is currently 340 seconds.
private const val usageThresholdMillis = 1000 * 340
/** /**
* Determines whether events should be tracked based on some general criteria: * Determines whether events should be tracked based on some general criteria:
* - user has installed as a result of a campaign * - user has installed as a result of a campaign

@ -11,12 +11,14 @@ import androidx.compose.material.Text
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.findNavController
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.transformWhile import kotlinx.coroutines.flow.transformWhile
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.lib.state.ext.flowScoped import mozilla.components.lib.state.ext.flowScoped
@ -29,6 +31,7 @@ import org.mozilla.fenix.compose.cfr.CFRPopupProperties
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.SupportUtils.SumoTopic.TOTAL_COOKIE_PROTECTION import org.mozilla.fenix.settings.SupportUtils.SumoTopic.TOTAL_COOKIE_PROTECTION
import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.dialog.CookieBannerReEngagementDialogUtils
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -111,6 +114,7 @@ class BrowserToolbarCFRPresenter(
true -> TrackingProtection.tcpCfrExplicitDismissal.record(NoExtras()) true -> TrackingProtection.tcpCfrExplicitDismissal.record(NoExtras())
false -> TrackingProtection.tcpCfrImplicitDismissal.record(NoExtras()) false -> TrackingProtection.tcpCfrImplicitDismissal.record(NoExtras())
} }
tryToShowCookieBannerDialogIfNeeded()
}, },
) { ) {
Text( Text(
@ -137,4 +141,15 @@ class BrowserToolbarCFRPresenter(
TrackingProtection.tcpCfrShown.record(NoExtras()) TrackingProtection.tcpCfrShown.record(NoExtras())
} }
} }
@VisibleForTesting
internal fun tryToShowCookieBannerDialogIfNeeded() {
browserStore.state.selectedTab?.let { tab ->
CookieBannerReEngagementDialogUtils.tryToShowReEngagementDialog(
settings = settings,
status = tab.cookieBanner,
navController = toolbar.findNavController(),
)
}
}
} }

@ -42,6 +42,7 @@ abstract class ToolbarIntegration(
toolbar, toolbar,
store, store,
sessionId, sessionId,
context.settings().showUnifiedSearchFeature,
ToolbarFeature.UrlRenderConfiguration( ToolbarFeature.UrlRenderConfiguration(
context.components.publicSuffixList, context.components.publicSuffixList,
ThemeManager.resolveAttribute(R.attr.textPrimary, context), ThemeManager.resolveAttribute(R.attr.textPrimary, context),

@ -4,7 +4,6 @@
package org.mozilla.fenix.compose package org.mozilla.fenix.compose
import android.content.res.Configuration
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -16,8 +15,8 @@ import androidx.compose.foundation.layout.width
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
/** /**
@ -39,8 +38,7 @@ fun Divider(
* An example of a vertical divider. * An example of a vertical divider.
*/ */
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun VerticalDividerPreview() { private fun VerticalDividerPreview() {
FirefoxTheme { FirefoxTheme {
Box( Box(
@ -74,8 +72,7 @@ private fun VerticalDividerPreview() {
* An example of divider usage in a list menu. * An example of divider usage in a list menu.
*/ */
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun HorizontalDividerPreview() { private fun HorizontalDividerPreview() {
FirefoxTheme { FirefoxTheme {
Box( Box(

@ -4,7 +4,6 @@
package org.mozilla.fenix.compose package org.mozilla.fenix.compose
import android.content.res.Configuration
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -22,9 +21,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
/** /**
@ -167,8 +166,7 @@ fun ListItemTabSurface(
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun ListItemTabLargePreview() { private fun ListItemTabLargePreview() {
FirefoxTheme { FirefoxTheme {
ListItemTabLarge( ListItemTabLarge(
@ -180,8 +178,7 @@ private fun ListItemTabLargePreview() {
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun ListItemTabSurfacePreview() { private fun ListItemTabSurfacePreview() {
FirefoxTheme { FirefoxTheme {
ListItemTabSurface( ListItemTabSurface(
@ -197,8 +194,7 @@ private fun ListItemTabSurfacePreview() {
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun ListItemTabSurfaceWithCustomBackgroundPreview() { private fun ListItemTabSurfaceWithCustomBackgroundPreview() {
FirefoxTheme { FirefoxTheme {
ListItemTabSurface( ListItemTabSurface(

@ -4,7 +4,6 @@
package org.mozilla.fenix.compose package org.mozilla.fenix.compose
import android.content.res.Configuration
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -26,10 +25,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.button.PrimaryButton import org.mozilla.fenix.compose.button.PrimaryButton
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
@ -199,8 +198,7 @@ data class MessageCardColors(
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun MessageCardPreview() { private fun MessageCardPreview() {
FirefoxTheme { FirefoxTheme {
Box( Box(
@ -219,8 +217,7 @@ private fun MessageCardPreview() {
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun MessageCardWithoutTitlePreview() { private fun MessageCardWithoutTitlePreview() {
FirefoxTheme { FirefoxTheme {
Box( Box(
@ -238,8 +235,7 @@ private fun MessageCardWithoutTitlePreview() {
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun MessageCardWithButtonLabelPreview() { private fun MessageCardWithButtonLabelPreview() {
FirefoxTheme { FirefoxTheme {
Box( Box(

@ -25,6 +25,7 @@ import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
/** /**
@ -74,8 +75,7 @@ fun SelectableChip(
} }
@Composable @Composable
@Preview(uiMode = UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = UI_MODE_NIGHT_NO)
private fun SelectableChipPreview() { private fun SelectableChipPreview() {
FirefoxTheme { FirefoxTheme {
Row( Row(

@ -0,0 +1,16 @@
/* 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.compose.annotation
import android.content.res.Configuration
import androidx.compose.ui.tooling.preview.Preview
/**
* A wrapper annotation for the two uiMode that are commonly used
* in Compose preview functions.
*/
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
annotation class LightDarkPreview

@ -4,7 +4,6 @@
package org.mozilla.fenix.compose.button package org.mozilla.fenix.compose.button
import android.content.res.Configuration
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -21,9 +20,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
/** /**
@ -183,8 +182,7 @@ fun DestructiveButton(
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun ButtonPreview() { private fun ButtonPreview() {
FirefoxTheme { FirefoxTheme {
Column( Column(

@ -4,14 +4,13 @@
package org.mozilla.fenix.compose.button package org.mozilla.fenix.compose.button
import android.content.res.Configuration
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
import java.util.Locale import java.util.Locale
@ -44,8 +43,7 @@ fun TextButton(
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun TextButtonPreview() { private fun TextButtonPreview() {
FirefoxTheme { FirefoxTheme {
Box(Modifier.background(FirefoxTheme.colors.layer1)) { Box(Modifier.background(FirefoxTheme.colors.layer1)) {

@ -4,7 +4,6 @@
package org.mozilla.fenix.compose.tabstray package org.mozilla.fenix.compose.tabstray
import android.content.res.Configuration
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -14,13 +13,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.google.accompanist.drawablepainter.rememberDrawablePainter
import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.state.createTab
import mozilla.components.concept.engine.mediasession.MediaSession.PlaybackState import mozilla.components.concept.engine.mediasession.MediaSession.PlaybackState
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
/** /**
@ -55,8 +54,7 @@ fun MediaImage(
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun ImagePreview() { private fun ImagePreview() {
FirefoxTheme { FirefoxTheme {
MediaImage( MediaImage(

@ -4,7 +4,6 @@
package org.mozilla.fenix.compose.tabstray package org.mozilla.fenix.compose.tabstray
import android.content.res.Configuration
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -38,7 +37,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.text.BidiFormatter import androidx.core.text.BidiFormatter
@ -49,6 +47,7 @@ import org.mozilla.fenix.compose.Divider
import org.mozilla.fenix.compose.Favicon import org.mozilla.fenix.compose.Favicon
import org.mozilla.fenix.compose.HorizontalFadingEdgeBox import org.mozilla.fenix.compose.HorizontalFadingEdgeBox
import org.mozilla.fenix.compose.ThumbnailCard import org.mozilla.fenix.compose.ThumbnailCard
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
/** /**
@ -231,8 +230,7 @@ private fun Thumbnail(
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun TabGridItemPreview() { private fun TabGridItemPreview() {
FirefoxTheme { FirefoxTheme {
TabGridItem( TabGridItem(
@ -249,8 +247,7 @@ private fun TabGridItemPreview() {
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun TabGridItemSelectedPreview() { private fun TabGridItemSelectedPreview() {
FirefoxTheme { FirefoxTheme {
TabGridItem( TabGridItem(
@ -265,8 +262,7 @@ private fun TabGridItemSelectedPreview() {
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun TabGridItemMultiSelectedPreview() { private fun TabGridItemMultiSelectedPreview() {
FirefoxTheme { FirefoxTheme {
TabGridItem( TabGridItem(

@ -4,7 +4,6 @@
package org.mozilla.fenix.compose.tabstray package org.mozilla.fenix.compose.tabstray
import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
@ -25,13 +24,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.state.createTab
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.compose.ThumbnailCard import org.mozilla.fenix.compose.ThumbnailCard
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
@ -168,8 +167,7 @@ private fun Thumbnail(
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun TabListItemPreview() { private fun TabListItemPreview() {
FirefoxTheme { FirefoxTheme {
TabListItem( TabListItem(
@ -183,8 +181,7 @@ private fun TabListItemPreview() {
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun SelectedTabListItemPreview() { private fun SelectedTabListItemPreview() {
FirefoxTheme { FirefoxTheme {
TabListItem( TabListItem(

@ -167,8 +167,11 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
val cookieBannersStorage = requireComponents.core.cookieBannersStorage val cookieBannersStorage = requireComponents.core.cookieBannersStorage
requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains -> requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val hasException = val hasException = if (requireContext().settings().shouldUseCookieBanner) {
cookieBannersStorage.hasException(tab.content.url, tab.content.private) cookieBannersStorage.hasException(tab.content.url, tab.content.private)
} else {
false
}
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
runIfFragmentIsAttached { runIfFragmentIsAttached {
val directions = ExternalAppBrowserFragmentDirections val directions = ExternalAppBrowserFragmentDirections

@ -0,0 +1,60 @@
/* 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.ext
import android.os.Build
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationManagerCompat
/**
* Returns whether notifications are enabled, catches any exception that was thrown from
* [NotificationManagerCompat.areNotificationsEnabled] and returns false.
*/
@Suppress("TooGenericExceptionCaught")
fun NotificationManagerCompat.areNotificationsEnabledSafe(): Boolean {
return try {
areNotificationsEnabled()
} catch (e: Exception) {
false
}
}
/**
* If the channel does not exist or is null, this returns false.
* If the channel exists with importance more than [NotificationManagerCompat.IMPORTANCE_NONE] and
* notifications are enabled for the app, this returns true.
* On <= SDK 26, this checks if notifications are enabled for the app.
*
* @param channelId the id of the notification channel to check.
* @return true if the channel is enabled, false otherwise.
*/
fun NotificationManagerCompat.isNotificationChannelEnabled(channelId: String): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = getNotificationChannelSafe(channelId)
if (channel == null) {
false
} else {
areNotificationsEnabledSafe() && channel.importance != NotificationManagerCompat.IMPORTANCE_NONE
}
} else {
areNotificationsEnabledSafe()
}
}
/**
* Returns the notification channel with the given [channelId], or null if the channel does not
* exist, catches any exception that was thrown by
* [NotificationManagerCompat.getNotificationChannelCompat] and returns null.
*
* @param channelId the id of the notification channel to check.
*/
@Suppress("TooGenericExceptionCaught")
private fun NotificationManagerCompat.getNotificationChannelSafe(channelId: String): NotificationChannelCompat? {
return try {
getNotificationChannelCompat(channelId)
} catch (e: Exception) {
null
}
}

@ -362,7 +362,7 @@ class HomeFragment : Fragment() {
} }
UnifiedSearch.searchMenuTapped.record(NoExtras()) UnifiedSearch.searchMenuTapped.record(NoExtras())
searchSelectorMenu.menuController.show(anchor = it, orientation = orientation, forceOrientation = true) searchSelectorMenu.menuController.show(anchor = it, orientation = orientation)
} }
} }
@ -418,7 +418,6 @@ class HomeFragment : Fragment() {
pocketStoriesController = DefaultPocketStoriesController( pocketStoriesController = DefaultPocketStoriesController(
homeActivity = activity, homeActivity = activity,
appStore = components.appStore, appStore = components.appStore,
navController = findNavController(),
), ),
) )

@ -62,17 +62,15 @@ private val expandedCollectionShape = RoundedCornerShape(topStart = 8.dp, topEnd
* @param menuItems List of [CollectionMenuItem] to be shown in a menu. * @param menuItems List of [CollectionMenuItem] to be shown in a menu.
* @param onToggleCollectionExpanded Invoked when the user clicks on the collection. * @param onToggleCollectionExpanded Invoked when the user clicks on the collection.
* @param onCollectionShareTabsClicked Invoked when the user clicks to share the collection. * @param onCollectionShareTabsClicked Invoked when the user clicks to share the collection.
* @param onCollectionMenuOpened Invoked when the user clicks to open a menu for the collection.
*/ */
@Composable @Composable
@Suppress("LongParameterList", "LongMethod") @Suppress("LongMethod")
fun Collection( fun Collection(
collection: TabCollection, collection: TabCollection,
expanded: Boolean, expanded: Boolean,
menuItems: List<CollectionMenuItem>, menuItems: List<CollectionMenuItem>,
onToggleCollectionExpanded: (TabCollection, Boolean) -> Unit, onToggleCollectionExpanded: (TabCollection, Boolean) -> Unit,
onCollectionShareTabsClicked: (TabCollection) -> Unit, onCollectionShareTabsClicked: (TabCollection) -> Unit,
onCollectionMenuOpened: () -> Unit,
) { ) {
var isMenuExpanded by remember(collection) { mutableStateOf(false) } var isMenuExpanded by remember(collection) { mutableStateOf(false) }
val isExpanded by remember(collection) { mutableStateOf(expanded) } val isExpanded by remember(collection) { mutableStateOf(expanded) }
@ -131,7 +129,6 @@ fun Collection(
IconButton( IconButton(
onClick = { onClick = {
isMenuExpanded = !isMenuExpanded isMenuExpanded = !isMenuExpanded
onCollectionMenuOpened()
}, },
) { ) {
Icon( Icon(
@ -165,7 +162,6 @@ private fun CollectionDarkPreview() {
menuItems = emptyList(), menuItems = emptyList(),
onToggleCollectionExpanded = { _, _ -> }, onToggleCollectionExpanded = { _, _ -> },
onCollectionShareTabsClicked = {}, onCollectionShareTabsClicked = {},
onCollectionMenuOpened = {},
) )
} }
} }
@ -180,7 +176,6 @@ private fun CollectionDarkExpandedPreview() {
menuItems = emptyList(), menuItems = emptyList(),
onToggleCollectionExpanded = { _, _ -> }, onToggleCollectionExpanded = { _, _ -> },
onCollectionShareTabsClicked = {}, onCollectionShareTabsClicked = {},
onCollectionMenuOpened = {},
) )
} }
} }
@ -195,7 +190,6 @@ private fun CollectionLightPreview() {
menuItems = emptyList(), menuItems = emptyList(),
onToggleCollectionExpanded = { _, _ -> }, onToggleCollectionExpanded = { _, _ -> },
onCollectionShareTabsClicked = {}, onCollectionShareTabsClicked = {},
onCollectionMenuOpened = {},
) )
} }
} }
@ -210,7 +204,6 @@ private fun CollectionLightExpandedPreview() {
menuItems = emptyList(), menuItems = emptyList(),
onToggleCollectionExpanded = { _, _ -> }, onToggleCollectionExpanded = { _, _ -> },
onCollectionShareTabsClicked = {}, onCollectionShareTabsClicked = {},
onCollectionMenuOpened = {},
) )
} }
} }

@ -5,7 +5,6 @@
package org.mozilla.fenix.home.collections package org.mozilla.fenix.home.collections
import android.content.Context import android.content.Context
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -35,7 +34,6 @@ import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import mozilla.components.browser.state.state.recover.RecoverableTab import mozilla.components.browser.state.state.recover.RecoverableTab
@ -43,6 +41,7 @@ import mozilla.components.concept.engine.Engine
import mozilla.components.feature.tab.collections.Tab import mozilla.components.feature.tab.collections.Tab
import org.mozilla.fenix.R.drawable import org.mozilla.fenix.R.drawable
import org.mozilla.fenix.R.string import org.mozilla.fenix.R.string
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.list.FaviconListItem import org.mozilla.fenix.compose.list.FaviconListItem
import org.mozilla.fenix.ext.toShortUrl import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
@ -191,8 +190,7 @@ private fun Modifier.clipTop() = this.then(
) )
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun TabInCollectionPreview() { private fun TabInCollectionPreview() {
FirefoxTheme { FirefoxTheme {
Column { Column {

@ -67,7 +67,6 @@ class CollectionViewHolder(
menuItems = menuItems, menuItems = menuItems,
onToggleCollectionExpanded = interactor::onToggleCollectionExpanded, onToggleCollectionExpanded = interactor::onToggleCollectionExpanded,
onCollectionShareTabsClicked = interactor::onCollectionShareTabsClicked, onCollectionShareTabsClicked = interactor::onCollectionShareTabsClicked,
onCollectionMenuOpened = interactor::onCollectionMenuOpened,
) )
} }
} }

@ -0,0 +1,40 @@
/* 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.home.intent
import android.content.Intent
import androidx.navigation.NavController
import androidx.navigation.navOptions
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.ext.nav
/**
* Long pressing home button should also open to the search fragment if Fenix is set as the
* assist app
*/
class AssistIntentProcessor : HomeIntentProcessor {
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
if (intent.action != Intent.ACTION_ASSIST) {
return false
}
val directions = NavGraphDirections.actionGlobalSearchDialog(
sessionId = null,
// Will follow this up with adding `ASSIST` as a search source.
// https://bugzilla.mozilla.org/show_bug.cgi?id=1808043
searchAccessPoint = MetricsUtils.Source.NONE,
)
val options = navOptions {
popUpTo(R.id.homeFragment)
}
navController.nav(null, directions, options)
return true
}
}

@ -4,7 +4,6 @@
package org.mozilla.fenix.home.pocket package org.mozilla.fenix.home.pocket
import android.content.res.Configuration
import android.view.View import android.view.View
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -15,7 +14,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -23,6 +21,7 @@ import mozilla.components.lib.state.ext.observeAsComposableState
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.components import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.ComposeViewHolder import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.home.HomeSectionHeader import org.mozilla.fenix.compose.home.HomeSectionHeader
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.wallpapers.WallpaperState import org.mozilla.fenix.wallpapers.WallpaperState
@ -127,8 +126,7 @@ private fun PocketTopics(
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun PocketCategoriesViewHolderPreview() { private fun PocketCategoriesViewHolderPreview() {
FirefoxTheme { FirefoxTheme {
PocketTopics( PocketTopics(

@ -6,7 +6,6 @@
package org.mozilla.fenix.home.pocket package org.mozilla.fenix.home.pocket
import android.content.res.Configuration
import android.view.View import android.view.View
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -18,7 +17,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -26,6 +24,7 @@ import mozilla.components.lib.state.ext.observeAsComposableState
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.components import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.ComposeViewHolder import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
/** /**
@ -79,8 +78,7 @@ class PocketRecommendationsHeaderViewHolder(
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun PocketRecommendationsFooterViewHolderPreview() { private fun PocketRecommendationsFooterViewHolderPreview() {
FirefoxTheme { FirefoxTheme {
Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) { Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) {

@ -4,8 +4,6 @@
package org.mozilla.fenix.home.pocket package org.mozilla.fenix.home.pocket
import androidx.annotation.VisibleForTesting
import androidx.navigation.NavController
import mozilla.components.service.glean.private.NoExtras import mozilla.components.service.glean.private.NoExtras
import mozilla.components.service.pocket.PocketStory import mozilla.components.service.pocket.PocketStory
import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory import mozilla.components.service.pocket.PocketStory.PocketRecommendedStory
@ -15,7 +13,6 @@ import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.GleanMetrics.Pings import org.mozilla.fenix.GleanMetrics.Pings
import org.mozilla.fenix.GleanMetrics.Pocket import org.mozilla.fenix.GleanMetrics.Pocket
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.components.appstate.AppAction
@ -73,12 +70,10 @@ interface PocketStoriesController {
* *
* @param homeActivity [HomeActivity] used to open URLs in a new tab. * @param homeActivity [HomeActivity] used to open URLs in a new tab.
* @param appStore [AppStore] from which to read the current Pocket recommendations and dispatch new actions on. * @param appStore [AppStore] from which to read the current Pocket recommendations and dispatch new actions on.
* @param navController [NavController] used for navigation.
*/ */
internal class DefaultPocketStoriesController( internal class DefaultPocketStoriesController(
private val homeActivity: HomeActivity, private val homeActivity: HomeActivity,
private val appStore: AppStore, private val appStore: AppStore,
private val navController: NavController,
) : PocketStoriesController { ) : PocketStoriesController {
override fun handleStoryShown( override fun handleStoryShown(
storyShown: PocketStory, storyShown: PocketStory,
@ -153,7 +148,6 @@ internal class DefaultPocketStoriesController(
storyClicked: PocketStory, storyClicked: PocketStory,
storyPosition: Pair<Int, Int>, storyPosition: Pair<Int, Int>,
) { ) {
dismissSearchDialogIfDisplayed()
homeActivity.openToBrowserAndLoad(storyClicked.url, true, BrowserDirection.FromHome) homeActivity.openToBrowserAndLoad(storyClicked.url, true, BrowserDirection.FromHome)
when (storyClicked) { when (storyClicked) {
@ -179,21 +173,12 @@ internal class DefaultPocketStoriesController(
} }
override fun handleLearnMoreClicked(link: String) { override fun handleLearnMoreClicked(link: String) {
dismissSearchDialogIfDisplayed()
homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome) homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome)
Pocket.homeRecsLearnMoreClicked.record(NoExtras()) Pocket.homeRecsLearnMoreClicked.record(NoExtras())
} }
override fun handleDiscoverMoreClicked(link: String) { override fun handleDiscoverMoreClicked(link: String) {
dismissSearchDialogIfDisplayed()
homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome) homeActivity.openToBrowserAndLoad(link, true, BrowserDirection.FromHome)
Pocket.homeRecsDiscoverClicked.record(NoExtras()) Pocket.homeRecsDiscoverClicked.record(NoExtras())
} }
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun dismissSearchDialogIfDisplayed() {
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigateUp()
}
}
} }

@ -4,8 +4,6 @@
package org.mozilla.fenix.home.recentbookmarks.controller package org.mozilla.fenix.home.recentbookmarks.controller
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
import androidx.navigation.NavController import androidx.navigation.NavController
import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.BookmarkRoot
import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineSession
@ -13,7 +11,6 @@ import mozilla.components.concept.engine.EngineSession.LoadUrlFlags.Companion.AL
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.GleanMetrics.RecentBookmarks import org.mozilla.fenix.GleanMetrics.RecentBookmarks
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.home.HomeFragmentDirections import org.mozilla.fenix.home.HomeFragmentDirections
@ -40,11 +37,6 @@ interface RecentBookmarksController {
* @see [RecentBookmarksInteractor.onRecentBookmarkRemoved] * @see [RecentBookmarksInteractor.onRecentBookmarkRemoved]
*/ */
fun handleBookmarkRemoved(bookmark: RecentBookmark) fun handleBookmarkRemoved(bookmark: RecentBookmark)
/**
* @see [RecentBookmarksInteractor.onRecentBookmarkLongClicked]
*/
fun handleBookmarkLongClicked()
} }
/** /**
@ -57,7 +49,6 @@ class DefaultRecentBookmarksController(
) : RecentBookmarksController { ) : RecentBookmarksController {
override fun handleBookmarkClicked(bookmark: RecentBookmark) { override fun handleBookmarkClicked(bookmark: RecentBookmark) {
dismissSearchDialogIfDisplayed()
activity.openToBrowserAndLoad( activity.openToBrowserAndLoad(
searchTermOrURL = bookmark.url!!, searchTermOrURL = bookmark.url!!,
newTab = true, newTab = true,
@ -69,7 +60,6 @@ class DefaultRecentBookmarksController(
override fun handleShowAllBookmarksClicked() { override fun handleShowAllBookmarksClicked() {
RecentBookmarks.showAllBookmarks.add() RecentBookmarks.showAllBookmarks.add()
dismissSearchDialogIfDisplayed()
navController.navigate( navController.navigate(
HomeFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id), HomeFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id),
) )
@ -78,15 +68,4 @@ class DefaultRecentBookmarksController(
override fun handleBookmarkRemoved(bookmark: RecentBookmark) { override fun handleBookmarkRemoved(bookmark: RecentBookmark) {
appStore.dispatch(AppAction.RemoveRecentBookmark(bookmark)) appStore.dispatch(AppAction.RemoveRecentBookmark(bookmark))
} }
override fun handleBookmarkLongClicked() {
dismissSearchDialogIfDisplayed()
}
@VisibleForTesting(otherwise = PRIVATE)
fun dismissSearchDialogIfDisplayed() {
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigateUp()
}
}
} }

@ -33,9 +33,4 @@ interface RecentBookmarksInteractor {
* @param bookmark The bookmark that has been removed. * @param bookmark The bookmark that has been removed.
*/ */
fun onRecentBookmarkRemoved(bookmark: RecentBookmark) fun onRecentBookmarkRemoved(bookmark: RecentBookmark)
/**
* Called when the user long clicks a recent bookmark.
*/
fun onRecentBookmarkLongClicked()
} }

@ -4,7 +4,6 @@
package org.mozilla.fenix.home.recentbookmarks.view package org.mozilla.fenix.home.recentbookmarks.view
import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -43,7 +42,6 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import mozilla.components.browser.icons.compose.Loader import mozilla.components.browser.icons.compose.Loader
import mozilla.components.browser.icons.compose.Placeholder import mozilla.components.browser.icons.compose.Placeholder
@ -51,6 +49,7 @@ import mozilla.components.browser.icons.compose.WithIcon
import mozilla.components.ui.colors.PhotonColors import mozilla.components.ui.colors.PhotonColors
import org.mozilla.fenix.components.components import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.Image import org.mozilla.fenix.compose.Image
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.inComposePreview import org.mozilla.fenix.compose.inComposePreview
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
@ -70,7 +69,6 @@ private val imageModifier = Modifier
* @param menuItems List of [RecentBookmarksMenuItem] shown when long clicking a [RecentBookmarkItem] * @param menuItems List of [RecentBookmarksMenuItem] shown when long clicking a [RecentBookmarkItem]
* @param backgroundColor The background [Color] of each bookmark. * @param backgroundColor The background [Color] of each bookmark.
* @param onRecentBookmarkClick Invoked when the user clicks on a recent bookmark. * @param onRecentBookmarkClick Invoked when the user clicks on a recent bookmark.
* @param onRecentBookmarkLongClick Invoked when the user long clicks on a recent bookmark.
*/ */
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
@ -79,7 +77,6 @@ fun RecentBookmarks(
menuItems: List<RecentBookmarksMenuItem>, menuItems: List<RecentBookmarksMenuItem>,
backgroundColor: Color, backgroundColor: Color,
onRecentBookmarkClick: (RecentBookmark) -> Unit = {}, onRecentBookmarkClick: (RecentBookmark) -> Unit = {},
onRecentBookmarkLongClick: () -> Unit = {},
) { ) {
LazyRow( LazyRow(
modifier = Modifier.semantics { modifier = Modifier.semantics {
@ -95,7 +92,6 @@ fun RecentBookmarks(
menuItems = menuItems, menuItems = menuItems,
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
onRecentBookmarkClick = onRecentBookmarkClick, onRecentBookmarkClick = onRecentBookmarkClick,
onRecentBookmarkLongClick = onRecentBookmarkLongClick,
) )
} }
} }
@ -108,7 +104,6 @@ fun RecentBookmarks(
* @param menuItems The list of [RecentBookmarksMenuItem] shown when long clicking on the recent bookmark item. * @param menuItems The list of [RecentBookmarksMenuItem] shown when long clicking on the recent bookmark item.
* @param backgroundColor The background [Color] of the recent bookmark item. * @param backgroundColor The background [Color] of the recent bookmark item.
* @param onRecentBookmarkClick Invoked when the user clicks on the recent bookmark item. * @param onRecentBookmarkClick Invoked when the user clicks on the recent bookmark item.
* @param onRecentBookmarkLongClick Invoked when the user long clicks on the recent bookmark item.
*/ */
@OptIn( @OptIn(
ExperimentalFoundationApi::class, ExperimentalFoundationApi::class,
@ -120,7 +115,6 @@ private fun RecentBookmarkItem(
menuItems: List<RecentBookmarksMenuItem>, menuItems: List<RecentBookmarksMenuItem>,
backgroundColor: Color, backgroundColor: Color,
onRecentBookmarkClick: (RecentBookmark) -> Unit = {}, onRecentBookmarkClick: (RecentBookmark) -> Unit = {},
onRecentBookmarkLongClick: () -> Unit = {},
) { ) {
var isMenuExpanded by remember { mutableStateOf(false) } var isMenuExpanded by remember { mutableStateOf(false) }
@ -130,10 +124,7 @@ private fun RecentBookmarkItem(
.combinedClickable( .combinedClickable(
enabled = true, enabled = true,
onClick = { onRecentBookmarkClick(bookmark) }, onClick = { onRecentBookmarkClick(bookmark) },
onLongClick = { onLongClick = { isMenuExpanded = true },
onRecentBookmarkLongClick()
isMenuExpanded = true
},
), ),
shape = cardShape, shape = cardShape,
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
@ -276,8 +267,7 @@ private fun RecentBookmarksMenu(
} }
@Composable @Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
private fun RecentBookmarksPreview() { private fun RecentBookmarksPreview() {
FirefoxTheme { FirefoxTheme {
RecentBookmarks( RecentBookmarks(

@ -14,7 +14,6 @@ import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.navigation.findNavController
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.compose.ComposeViewHolder import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.compose.home.HomeSectionHeader import org.mozilla.fenix.compose.home.HomeSectionHeader
@ -38,13 +37,6 @@ class RecentBookmarksHeaderViewHolder(
composeView.setPadding(horizontalPadding, 0, horizontalPadding, 0) composeView.setPadding(horizontalPadding, 0, horizontalPadding, 0)
} }
private fun dismissSearchDialogIfDisplayed() {
val navController = itemView.findNavController()
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigateUp()
}
}
@Composable @Composable
override fun Content() { override fun Content() {
Column { Column {
@ -54,7 +46,6 @@ class RecentBookmarksHeaderViewHolder(
headerText = stringResource(R.string.recently_saved_title), headerText = stringResource(R.string.recently_saved_title),
description = stringResource(R.string.recently_saved_show_all_content_description_2), description = stringResource(R.string.recently_saved_show_all_content_description_2),
onShowAllClick = { onShowAllClick = {
dismissSearchDialogIfDisplayed()
interactor.onShowAllBookmarksClicked() interactor.onShowAllBookmarksClicked()
}, },
) )

@ -48,7 +48,6 @@ class RecentBookmarksViewHolder(
onClick = { bookmark -> interactor.onRecentBookmarkRemoved(bookmark) }, onClick = { bookmark -> interactor.onRecentBookmarkRemoved(bookmark) },
), ),
), ),
onRecentBookmarkLongClick = interactor::onRecentBookmarkLongClicked,
) )
} }
} }

@ -25,11 +25,6 @@ interface RecentSyncedTabController {
*/ */
fun handleRecentSyncedTabClick(tab: RecentSyncedTab) fun handleRecentSyncedTabClick(tab: RecentSyncedTab)
/**
* @see [RecentSyncedTabInteractor.onRecentSyncedTabLongClick]
*/
fun handleRecentSyncedTabLongClick()
/** /**
* @see [RecentSyncedTabInteractor.onRecentSyncedTabClicked] * @see [RecentSyncedTabInteractor.onRecentSyncedTabClicked]
*/ */
@ -71,12 +66,6 @@ class DefaultRecentSyncedTabController(
) )
} }
override fun handleRecentSyncedTabLongClick() {
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigateUp()
}
}
override fun handleRecentSyncedTabRemoved(tab: RecentSyncedTab) { override fun handleRecentSyncedTabRemoved(tab: RecentSyncedTab) {
appStore.dispatch(AppAction.RemoveRecentSyncedTab(tab)) appStore.dispatch(AppAction.RemoveRecentSyncedTab(tab))
} }

@ -17,11 +17,6 @@ interface RecentSyncedTabInteractor {
*/ */
fun onRecentSyncedTabClicked(tab: RecentSyncedTab) fun onRecentSyncedTabClicked(tab: RecentSyncedTab)
/**
* Called when opening the dropdown menu on a recent synced tab by long press.
*/
fun onRecentSyncedTabLongClick()
/** /**
* Opens the tabs tray to the synced tab page. Called when a user clicks on the "See all synced * Opens the tabs tray to the synced tab page. Called when a user clicks on the "See all synced
* tabs" button. * tabs" button.

@ -63,7 +63,6 @@ import org.mozilla.fenix.theme.FirefoxTheme
* @param onRecentSyncedTabClick Invoked when the user clicks on the recent synced tab. * @param onRecentSyncedTabClick Invoked when the user clicks on the recent synced tab.
* @param onSeeAllSyncedTabsButtonClick Invoked when user clicks on the "See all" button in the synced tab card. * @param onSeeAllSyncedTabsButtonClick Invoked when user clicks on the "See all" button in the synced tab card.
* @param onRemoveSyncedTab Invoked when user clicks on the "Remove" dropdown menu option. * @param onRemoveSyncedTab Invoked when user clicks on the "Remove" dropdown menu option.
* @param onRecentSyncedTabLongClick Invoked when user long presses the recent synced tab.
*/ */
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Suppress("LongMethod", "LongParameterList") @Suppress("LongMethod", "LongParameterList")
@ -76,7 +75,6 @@ fun RecentSyncedTab(
onRecentSyncedTabClick: (RecentSyncedTab) -> Unit, onRecentSyncedTabClick: (RecentSyncedTab) -> Unit,
onSeeAllSyncedTabsButtonClick: () -> Unit, onSeeAllSyncedTabsButtonClick: () -> Unit,
onRemoveSyncedTab: (RecentSyncedTab) -> Unit, onRemoveSyncedTab: (RecentSyncedTab) -> Unit,
onRecentSyncedTabLongClick: () -> Unit,
) { ) {
var isDropdownExpanded by remember { mutableStateOf(false) } var isDropdownExpanded by remember { mutableStateOf(false) }
@ -91,10 +89,7 @@ fun RecentSyncedTab(
.height(180.dp) .height(180.dp)
.combinedClickable( .combinedClickable(
onClick = { tab?.let { onRecentSyncedTabClick(tab) } }, onClick = { tab?.let { onRecentSyncedTabClick(tab) } },
onLongClick = { onLongClick = { isDropdownExpanded = true },
onRecentSyncedTabLongClick()
isDropdownExpanded = true
},
), ),
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
@ -292,7 +287,6 @@ private fun LoadedRecentSyncedTab() {
onRecentSyncedTabClick = {}, onRecentSyncedTabClick = {},
onSeeAllSyncedTabsButtonClick = {}, onSeeAllSyncedTabsButtonClick = {},
onRemoveSyncedTab = {}, onRemoveSyncedTab = {},
onRecentSyncedTabLongClick = {},
) )
} }
} }
@ -307,7 +301,6 @@ private fun LoadingRecentSyncedTab() {
onRecentSyncedTabClick = {}, onRecentSyncedTabClick = {},
onSeeAllSyncedTabsButtonClick = {}, onSeeAllSyncedTabsButtonClick = {},
onRemoveSyncedTab = {}, onRemoveSyncedTab = {},
onRecentSyncedTabLongClick = {},
) )
} }
} }

@ -77,7 +77,6 @@ class RecentSyncedTabViewHolder(
onRecentSyncedTabClick = recentSyncedTabInteractor::onRecentSyncedTabClicked, onRecentSyncedTabClick = recentSyncedTabInteractor::onRecentSyncedTabClicked,
onSeeAllSyncedTabsButtonClick = recentSyncedTabInteractor::onSyncedTabShowAllClicked, onSeeAllSyncedTabsButtonClick = recentSyncedTabInteractor::onSyncedTabShowAllClicked,
onRemoveSyncedTab = recentSyncedTabInteractor::onRemovedRecentSyncedTab, onRemoveSyncedTab = recentSyncedTabInteractor::onRemovedRecentSyncedTab,
onRecentSyncedTabLongClick = recentSyncedTabInteractor::onRecentSyncedTabLongClick,
) )
} }
} }

@ -4,8 +4,6 @@
package org.mozilla.fenix.home.recenttabs.controller package org.mozilla.fenix.home.recenttabs.controller
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
import androidx.navigation.NavController import androidx.navigation.NavController
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.feature.tabs.TabsUseCases.SelectTabUseCase import mozilla.components.feature.tabs.TabsUseCases.SelectTabUseCase
@ -29,11 +27,6 @@ interface RecentTabController {
*/ */
fun handleRecentTabClicked(tabId: String) fun handleRecentTabClicked(tabId: String)
/**
* @see [RecentTabInteractor.onRecentTabLongClicked]
*/
fun handleRecentTabLongClicked()
/** /**
* @see [RecentTabInteractor.onRecentTabShowAllClicked] * @see [RecentTabInteractor.onRecentTabShowAllClicked]
*/ */
@ -69,12 +62,7 @@ class DefaultRecentTabsController(
navController.navigate(R.id.browserFragment) navController.navigate(R.id.browserFragment)
} }
override fun handleRecentTabLongClicked() {
dismissSearchDialogIfDisplayed()
}
override fun handleRecentTabShowAllClicked() { override fun handleRecentTabShowAllClicked() {
dismissSearchDialogIfDisplayed()
RecentTabs.showAllClicked.record(NoExtras()) RecentTabs.showAllClicked.record(NoExtras())
navController.navigate(HomeFragmentDirections.actionGlobalTabsTrayFragment()) navController.navigate(HomeFragmentDirections.actionGlobalTabsTrayFragment())
} }
@ -82,11 +70,4 @@ class DefaultRecentTabsController(
override fun handleRecentTabRemoved(tab: RecentTab.Tab) { override fun handleRecentTabRemoved(tab: RecentTab.Tab) {
appStore.dispatch(AppAction.RemoveRecentTab(tab)) appStore.dispatch(AppAction.RemoveRecentTab(tab))
} }
@VisibleForTesting(otherwise = PRIVATE)
fun dismissSearchDialogIfDisplayed() {
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigateUp()
}
}
} }

@ -17,11 +17,6 @@ interface RecentTabInteractor {
*/ */
fun onRecentTabClicked(tabId: String) fun onRecentTabClicked(tabId: String)
/**
* Called when the user long clicks on a recent tab.
*/
fun onRecentTabLongClicked()
/** /**
* Show the tabs tray. Called when a user clicks on the "Show all" button besides the recent * Show the tabs tray. Called when a user clicks on the "Show all" button besides the recent
* tabs. * tabs.

@ -50,7 +50,6 @@ class RecentTabViewHolder(
recentTabs = recentTabs.value ?: emptyList(), recentTabs = recentTabs.value ?: emptyList(),
backgroundColor = wallpaperState.wallpaperCardColor, backgroundColor = wallpaperState.wallpaperCardColor,
onRecentTabClick = { recentTabInteractor.onRecentTabClicked(it) }, onRecentTabClick = { recentTabInteractor.onRecentTabClicked(it) },
onRecentTabLongClick = { recentTabInteractor.onRecentTabLongClicked() },
menuItems = listOf( menuItems = listOf(
RecentTabMenuItem( RecentTabMenuItem(
title = stringResource(id = R.string.recent_tab_menu_item_remove), title = stringResource(id = R.string.recent_tab_menu_item_remove),

@ -6,7 +6,6 @@
package org.mozilla.fenix.home.recenttabs.view package org.mozilla.fenix.home.recenttabs.view
import android.content.res.Configuration
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
@ -49,7 +48,6 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import mozilla.components.browser.icons.compose.Loader import mozilla.components.browser.icons.compose.Loader
@ -62,6 +60,7 @@ import mozilla.components.ui.colors.PhotonColors
import org.mozilla.fenix.components.components import org.mozilla.fenix.components.components
import org.mozilla.fenix.compose.Image import org.mozilla.fenix.compose.Image
import org.mozilla.fenix.compose.ThumbnailCard import org.mozilla.fenix.compose.ThumbnailCard
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.inComposePreview import org.mozilla.fenix.compose.inComposePreview
import org.mozilla.fenix.home.recenttabs.RecentTab import org.mozilla.fenix.home.recenttabs.RecentTab
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
@ -81,7 +80,6 @@ fun RecentTabs(
menuItems: List<RecentTabMenuItem>, menuItems: List<RecentTabMenuItem>,
backgroundColor: Color = FirefoxTheme.colors.layer2, backgroundColor: Color = FirefoxTheme.colors.layer2,
onRecentTabClick: (String) -> Unit = {}, onRecentTabClick: (String) -> Unit = {},
onRecentTabLongClick: () -> Unit = {},
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@ -100,7 +98,6 @@ fun RecentTabs(
menuItems = menuItems, menuItems = menuItems,
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
onRecentTabClick = onRecentTabClick, onRecentTabClick = onRecentTabClick,
onRecentTabLongClick = onRecentTabLongClick,
) )
} }
} }
@ -126,7 +123,6 @@ private fun RecentTabItem(
menuItems: List<RecentTabMenuItem>, menuItems: List<RecentTabMenuItem>,
backgroundColor: Color, backgroundColor: Color,
onRecentTabClick: (String) -> Unit = {}, onRecentTabClick: (String) -> Unit = {},
onRecentTabLongClick: () -> Unit = {},
) { ) {
var isMenuExpanded by remember { mutableStateOf(false) } var isMenuExpanded by remember { mutableStateOf(false) }
@ -137,10 +133,7 @@ private fun RecentTabItem(
.combinedClickable( .combinedClickable(
enabled = true, enabled = true,
onClick = { onRecentTabClick(tab.state.id) }, onClick = { onRecentTabClick(tab.state.id) },
onLongClick = { onLongClick = { isMenuExpanded = true },
onRecentTabLongClick()
isMenuExpanded = true
},
), ),
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
@ -363,8 +356,7 @@ private fun PlaceHolderTabIcon(modifier: Modifier) {
) )
} }
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @LightDarkPreview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Composable @Composable
private fun RecentTabsPreview() { private fun RecentTabsPreview() {
val tab = RecentTab.Tab( val tab = RecentTab.Tab(

@ -14,7 +14,6 @@ import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.navigation.findNavController
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.compose.ComposeViewHolder import org.mozilla.fenix.compose.ComposeViewHolder
import org.mozilla.fenix.compose.home.HomeSectionHeader import org.mozilla.fenix.compose.home.HomeSectionHeader
@ -37,13 +36,6 @@ class RecentTabsHeaderViewHolder(
composeView.setPadding(horizontalPadding, 0, horizontalPadding, 0) composeView.setPadding(horizontalPadding, 0, horizontalPadding, 0)
} }
private fun dismissSearchDialogIfDisplayed() {
val navController = itemView.findNavController()
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigateUp()
}
}
@Composable @Composable
override fun Content() { override fun Content() {
Column { Column {
@ -53,7 +45,6 @@ class RecentTabsHeaderViewHolder(
headerText = stringResource(R.string.recent_tabs_header), headerText = stringResource(R.string.recent_tabs_header),
description = stringResource(R.string.recent_tabs_show_all_content_description_2), description = stringResource(R.string.recent_tabs_show_all_content_description_2),
onShowAllClick = { onShowAllClick = {
dismissSearchDialogIfDisplayed()
interactor.onRecentTabShowAllClicked() interactor.onRecentTabShowAllClicked()
}, },
) )

@ -4,8 +4,6 @@
package org.mozilla.fenix.home.recentvisits.controller package org.mozilla.fenix.home.recentvisits.controller
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
import androidx.navigation.NavController import androidx.navigation.NavController
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -60,11 +58,6 @@ interface RecentVisitsController {
* @param highlightUrl Url of the [RecentHistoryHighlight] to remove. * @param highlightUrl Url of the [RecentHistoryHighlight] to remove.
*/ */
fun handleRemoveRecentHistoryHighlight(highlightUrl: String) fun handleRemoveRecentHistoryHighlight(highlightUrl: String)
/**
* Callback for when the user long clicks on a recent visit.
*/
fun handleRecentVisitLongClicked()
} }
/** /**
@ -83,7 +76,6 @@ class DefaultRecentVisitsController(
* Shows the history fragment. * Shows the history fragment.
*/ */
override fun handleHistoryShowAllClicked() { override fun handleHistoryShowAllClicked() {
dismissSearchDialogIfDisplayed()
navController.navigate( navController.navigate(
HomeFragmentDirections.actionGlobalHistoryFragment(), HomeFragmentDirections.actionGlobalHistoryFragment(),
) )
@ -144,18 +136,4 @@ class DefaultRecentVisitsController(
storage.deleteHistoryMetadataForUrl(highlightUrl) storage.deleteHistoryMetadataForUrl(highlightUrl)
} }
} }
/**
* Dismiss the search dialog if displayed.
*/
override fun handleRecentVisitLongClicked() {
dismissSearchDialogIfDisplayed()
}
@VisibleForTesting(otherwise = PRIVATE)
fun dismissSearchDialogIfDisplayed() {
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigateUp()
}
}
} }

@ -44,9 +44,4 @@ interface RecentVisitsInteractor {
* @param highlightUrl [RecentHistoryHighlight.url] of the item to remove. * @param highlightUrl [RecentHistoryHighlight.url] of the item to remove.
*/ */
fun onRemoveRecentHistoryHighlight(highlightUrl: String) fun onRemoveRecentHistoryHighlight(highlightUrl: String)
/**
* Called when opening the dropdown menu on a recent visit by long press.
*/
fun onRecentVisitLongClicked()
} }

@ -70,7 +70,6 @@ private const val VISITS_PER_COLUMN = 3
* @param menuItems List of [RecentVisitMenuItem] shown long clicking a [RecentlyVisitedItem]. * @param menuItems List of [RecentVisitMenuItem] shown long clicking a [RecentlyVisitedItem].
* @param backgroundColor The background [Color] of each item. * @param backgroundColor The background [Color] of each item.
* @param onRecentVisitClick Invoked when the user clicks on a recent visit. * @param onRecentVisitClick Invoked when the user clicks on a recent visit.
* @param onRecentVisitLongClick Invoked when the user long clicks on a recent visit.
*/ */
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
@Composable @Composable
@ -79,7 +78,6 @@ fun RecentlyVisited(
menuItems: List<RecentVisitMenuItem>, menuItems: List<RecentVisitMenuItem>,
backgroundColor: Color = FirefoxTheme.colors.layer2, backgroundColor: Color = FirefoxTheme.colors.layer2,
onRecentVisitClick: (RecentlyVisitedItem, Int) -> Unit = { _, _ -> }, onRecentVisitClick: (RecentlyVisitedItem, Int) -> Unit = { _, _ -> },
onRecentVisitLongClick: () -> Unit = {},
) { ) {
Card( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -116,7 +114,6 @@ fun RecentlyVisited(
onRecentVisitClick = { onRecentVisitClick = {
onRecentVisitClick(it, pageIndex + 1) onRecentVisitClick(it, pageIndex + 1)
}, },
onRecentVisitLongClick = { onRecentVisitLongClick() },
) )
is RecentHistoryGroup -> RecentlyVisitedHistoryGroup( is RecentHistoryGroup -> RecentlyVisitedHistoryGroup(
recentVisit = recentVisit, recentVisit = recentVisit,
@ -126,7 +123,6 @@ fun RecentlyVisited(
onRecentVisitClick = { onRecentVisitClick = {
onRecentVisitClick(it, pageIndex + 1) onRecentVisitClick(it, pageIndex + 1)
}, },
onRecentVisitLongClick = { onRecentVisitLongClick() },
) )
} }
} }
@ -144,13 +140,11 @@ fun RecentlyVisited(
* @param clickableEnabled Whether click actions should be invoked or not. * @param clickableEnabled Whether click actions should be invoked or not.
* @param showDividerLine Whether to show a divider line at the bottom. * @param showDividerLine Whether to show a divider line at the bottom.
* @param onRecentVisitClick Invoked when the user clicks on a recent visit. * @param onRecentVisitClick Invoked when the user clicks on a recent visit.
* @param onRecentVisitClick Invoked when the user long clicks on a recently visited group.
*/ */
@OptIn( @OptIn(
ExperimentalFoundationApi::class, ExperimentalFoundationApi::class,
ExperimentalComposeUiApi::class, ExperimentalComposeUiApi::class,
) )
@Suppress("LongParameterList")
@Composable @Composable
private fun RecentlyVisitedHistoryGroup( private fun RecentlyVisitedHistoryGroup(
recentVisit: RecentHistoryGroup, recentVisit: RecentHistoryGroup,
@ -158,7 +152,6 @@ private fun RecentlyVisitedHistoryGroup(
clickableEnabled: Boolean, clickableEnabled: Boolean,
showDividerLine: Boolean, showDividerLine: Boolean,
onRecentVisitClick: (RecentHistoryGroup) -> Unit = { _ -> }, onRecentVisitClick: (RecentHistoryGroup) -> Unit = { _ -> },
onRecentVisitLongClick: () -> Unit = {},
) { ) {
var isMenuExpanded by remember { mutableStateOf(false) } var isMenuExpanded by remember { mutableStateOf(false) }
@ -167,10 +160,7 @@ private fun RecentlyVisitedHistoryGroup(
.combinedClickable( .combinedClickable(
enabled = clickableEnabled, enabled = clickableEnabled,
onClick = { onRecentVisitClick(recentVisit) }, onClick = { onRecentVisitClick(recentVisit) },
onLongClick = { onLongClick = { isMenuExpanded = true },
onRecentVisitLongClick()
isMenuExpanded = true
},
) )
.size(268.dp, 56.dp) .size(268.dp, 56.dp)
.semantics { .semantics {
@ -233,13 +223,11 @@ private fun RecentlyVisitedHistoryGroup(
* @param clickableEnabled Whether click actions should be invoked or not. * @param clickableEnabled Whether click actions should be invoked or not.
* @param showDividerLine Whether to show a divider line at the bottom. * @param showDividerLine Whether to show a divider line at the bottom.
* @param onRecentVisitClick Invoked when the user clicks on a recent visit. * @param onRecentVisitClick Invoked when the user clicks on a recent visit.
* @param onRecentVisitLongClick Invoked when the user long clicks on a recent visit highlight.
*/ */
@OptIn( @OptIn(
ExperimentalFoundationApi::class, ExperimentalFoundationApi::class,
ExperimentalComposeUiApi::class, ExperimentalComposeUiApi::class,
) )
@Suppress("LongParameterList")
@Composable @Composable
private fun RecentlyVisitedHistoryHighlight( private fun RecentlyVisitedHistoryHighlight(
recentVisit: RecentHistoryHighlight, recentVisit: RecentHistoryHighlight,
@ -247,7 +235,6 @@ private fun RecentlyVisitedHistoryHighlight(
clickableEnabled: Boolean, clickableEnabled: Boolean,
showDividerLine: Boolean, showDividerLine: Boolean,
onRecentVisitClick: (RecentHistoryHighlight) -> Unit = { _ -> }, onRecentVisitClick: (RecentHistoryHighlight) -> Unit = { _ -> },
onRecentVisitLongClick: () -> Unit = {},
) { ) {
var isMenuExpanded by remember { mutableStateOf(false) } var isMenuExpanded by remember { mutableStateOf(false) }
@ -256,10 +243,7 @@ private fun RecentlyVisitedHistoryHighlight(
.combinedClickable( .combinedClickable(
enabled = clickableEnabled, enabled = clickableEnabled,
onClick = { onRecentVisitClick(recentVisit) }, onClick = { onRecentVisitClick(recentVisit) },
onLongClick = { onLongClick = { isMenuExpanded = true },
onRecentVisitLongClick()
isMenuExpanded = true
},
) )
.size(268.dp, 56.dp) .size(268.dp, 56.dp)
.semantics { .semantics {

@ -80,7 +80,6 @@ class RecentlyVisitedViewHolder(
} }
} }
}, },
onRecentVisitLongClick = { interactor.onRecentVisitLongClicked() },
) )
} }

@ -41,7 +41,6 @@ import org.mozilla.fenix.GleanMetrics.TopSites
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator import org.mozilla.fenix.browser.BrowserAnimator
import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.collections.SaveCollectionStep import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.AppStore
@ -178,11 +177,6 @@ interface SessionControlController {
*/ */
fun handleRemoveCollectionsPlaceholder() fun handleRemoveCollectionsPlaceholder()
/**
* @see [CollectionInteractor.onCollectionMenuOpened] and [TopSiteInteractor.onTopSiteMenuOpened]
*/
fun handleMenuOpened()
/** /**
* @see [MessageCardInteractor.onMessageClicked] * @see [MessageCardInteractor.onMessageClicked]
*/ */
@ -248,13 +242,7 @@ class DefaultSessionControlController(
) )
} }
override fun handleMenuOpened() {
dismissSearchDialogIfDisplayed()
}
override fun handleCollectionOpenTabClicked(tab: ComponentTab) { override fun handleCollectionOpenTabClicked(tab: ComponentTab) {
dismissSearchDialogIfDisplayed()
restoreUseCase.invoke( restoreUseCase.invoke(
activity, activity,
engine, engine,
@ -307,7 +295,6 @@ class DefaultSessionControlController(
} }
override fun handleCollectionShareTabsClicked(collection: TabCollection) { override fun handleCollectionShareTabsClicked(collection: TabCollection) {
dismissSearchDialogIfDisplayed()
showShareFragment( showShareFragment(
collection.title, collection.title,
collection.tabs.map { ShareData(url = it.url, title = it.title) }, collection.tabs.map { ShareData(url = it.url, title = it.title) },
@ -337,7 +324,6 @@ class DefaultSessionControlController(
} }
override fun handlePrivateBrowsingLearnMoreClicked() { override fun handlePrivateBrowsingLearnMoreClicked() {
dismissSearchDialogIfDisplayed()
activity.openToBrowserAndLoad( activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic(PRIVATE_BROWSING_MYTHS), searchTermOrURL = SupportUtils.getGenericSumoURLForTopic(PRIVATE_BROWSING_MYTHS),
newTab = true, newTab = true,
@ -403,8 +389,6 @@ class DefaultSessionControlController(
} }
override fun handleSelectTopSite(topSite: TopSite, position: Int) { override fun handleSelectTopSite(topSite: TopSite, position: Int) {
dismissSearchDialogIfDisplayed()
TopSites.openInNewTab.record(NoExtras()) TopSites.openInNewTab.record(NoExtras())
when (topSite) { when (topSite) {
@ -501,12 +485,6 @@ class DefaultSessionControlController(
return url return url
} }
private fun dismissSearchDialogIfDisplayed() {
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigateUp()
}
}
override fun handleStartBrowsingClicked() { override fun handleStartBrowsingClicked() {
hideOnboarding() hideOnboarding()
} }
@ -648,14 +626,6 @@ class DefaultSessionControlController(
appStore.dispatch( appStore.dispatch(
AppAction.ModeChange(Mode.fromBrowsingMode(newMode)), AppAction.ModeChange(Mode.fromBrowsingMode(newMode)),
) )
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigate(
BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = null,
),
)
}
} }
} }

@ -133,11 +133,6 @@ interface CollectionInteractor {
* User has removed the collections placeholder from home. * User has removed the collections placeholder from home.
*/ */
fun onRemoveCollectionsPlaceholder() fun onRemoveCollectionsPlaceholder()
/**
* User has opened collection 3 dot menu.
*/
fun onCollectionMenuOpened()
} }
interface ToolbarInteractor { interface ToolbarInteractor {
@ -228,11 +223,6 @@ interface TopSiteInteractor {
* "Our sponsors & your privacy" top site menu item. * "Our sponsors & your privacy" top site menu item.
*/ */
fun onSponsorPrivacyClicked() fun onSponsorPrivacyClicked()
/**
* Called when top site menu is opened.
*/
fun onTopSiteMenuOpened()
} }
interface MessageCardInteractor { interface MessageCardInteractor {
@ -367,14 +357,6 @@ class SessionControlInteractor(
controller.handleRemoveCollectionsPlaceholder() controller.handleRemoveCollectionsPlaceholder()
} }
override fun onCollectionMenuOpened() {
controller.handleMenuOpened()
}
override fun onTopSiteMenuOpened() {
controller.handleMenuOpened()
}
override fun onRecentTabClicked(tabId: String) { override fun onRecentTabClicked(tabId: String) {
recentTabController.handleRecentTabClicked(tabId) recentTabController.handleRecentTabClicked(tabId)
} }
@ -383,10 +365,6 @@ class SessionControlInteractor(
recentTabController.handleRecentTabShowAllClicked() recentTabController.handleRecentTabShowAllClicked()
} }
override fun onRecentTabLongClicked() {
recentTabController.handleRecentTabLongClicked()
}
override fun onRemoveRecentTab(tab: RecentTab.Tab) { override fun onRemoveRecentTab(tab: RecentTab.Tab) {
recentTabController.handleRecentTabRemoved(tab) recentTabController.handleRecentTabRemoved(tab)
} }
@ -395,10 +373,6 @@ class SessionControlInteractor(
recentSyncedTabController.handleRecentSyncedTabClick(tab) recentSyncedTabController.handleRecentSyncedTabClick(tab)
} }
override fun onRecentSyncedTabLongClick() {
recentSyncedTabController.handleRecentSyncedTabLongClick()
}
override fun onSyncedTabShowAllClicked() { override fun onSyncedTabShowAllClicked() {
recentSyncedTabController.handleSyncedTabShowAllClicked() recentSyncedTabController.handleSyncedTabShowAllClicked()
} }
@ -419,10 +393,6 @@ class SessionControlInteractor(
recentBookmarksController.handleBookmarkRemoved(bookmark) recentBookmarksController.handleBookmarkRemoved(bookmark)
} }
override fun onRecentBookmarkLongClicked() {
recentBookmarksController.handleBookmarkLongClicked()
}
override fun onHistoryShowAllClicked() { override fun onHistoryShowAllClicked() {
recentVisitsController.handleHistoryShowAllClicked() recentVisitsController.handleHistoryShowAllClicked()
} }
@ -445,10 +415,6 @@ class SessionControlInteractor(
recentVisitsController.handleRemoveRecentHistoryHighlight(highlightUrl) recentVisitsController.handleRemoveRecentHistoryHighlight(highlightUrl)
} }
override fun onRecentVisitLongClicked() {
recentVisitsController.handleRecentVisitLongClicked()
}
override fun openCustomizeHomePage() { override fun openCustomizeHomePage() {
controller.handleCustomizeHomeTapped() controller.handleCustomizeHomeTapped()
} }

@ -54,7 +54,6 @@ class TopSiteItemViewHolder(
init { init {
itemView.setOnLongClickListener { itemView.setOnLongClickListener {
interactor.onTopSiteMenuOpened()
TopSites.longPress.record(TopSites.LongPressExtra(topSite.name())) TopSites.longPress.record(TopSites.LongPressExtra(topSite.name()))
val topSiteMenu = TopSiteItemMenu( val topSiteMenu = TopSiteItemMenu(

@ -81,6 +81,8 @@ class ToolbarView(
false false
} }
setDefaultIcon()
setOnEditListener( setOnEditListener(
object : mozilla.components.concept.toolbar.Toolbar.OnEditListener { object : mozilla.components.concept.toolbar.Toolbar.OnEditListener {
override fun onCancelEditing(): Boolean { override fun onCancelEditing(): Boolean {
@ -110,7 +112,9 @@ class ToolbarView(
view.editMode() view.editMode()
isInitialized = true isInitialized = true
} }
}
private fun setDefaultIcon() {
val bookmarkSearchIcon = val bookmarkSearchIcon =
AppCompatResources.getDrawable(context, R.drawable.ic_bookmarks_menu) AppCompatResources.getDrawable(context, R.drawable.ic_bookmarks_menu)

@ -81,6 +81,8 @@ class ToolbarView(
false false
} }
setDefaultIcon()
setOnEditListener( setOnEditListener(
object : mozilla.components.concept.toolbar.Toolbar.OnEditListener { object : mozilla.components.concept.toolbar.Toolbar.OnEditListener {
override fun onCancelEditing(): Boolean { override fun onCancelEditing(): Boolean {
@ -110,7 +112,9 @@ class ToolbarView(
view.editMode() view.editMode()
isInitialized = true isInitialized = true
} }
}
private fun setDefaultIcon() {
val historySearchIcon = AppCompatResources.getDrawable(context, R.drawable.ic_history) val historySearchIcon = AppCompatResources.getDrawable(context, R.drawable.ic_history)
historySearchIcon?.let { historySearchIcon?.let {

@ -0,0 +1,64 @@
/* 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.onboarding
import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.DialogFragment
import com.google.accompanist.insets.ProvideWindowInsets
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.onboarding.view.NotificationPermissionDialogScreen
import org.mozilla.fenix.theme.FirefoxTheme
/**
* Dialog displaying notification pre-permission prompt.
*/
class HomeNotificationPermissionDialogFragment : DialogFragment() {
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.HomeOnboardingDialogStyle)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
override fun onDestroy() {
super.onDestroy()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View = ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
ProvideWindowInsets {
FirefoxTheme {
NotificationPermissionDialogScreen(
onDismiss = ::onDismiss,
grantNotificationPermission = {
ensureMarketingChannelExists(context.applicationContext)
onDismiss()
},
)
}
}
}
}
private fun onDismiss() {
dismiss()
context?.settings()?.isNotificationPrePermissionShown = true
}
}

@ -8,13 +8,11 @@ import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import androidx.core.app.NotificationManagerCompat
import org.mozilla.fenix.GleanMetrics.Events.marketingNotificationAllowed
import org.mozilla.fenix.R import org.mozilla.fenix.R
// Channel ID was not updated when it was renamed to marketing. Thus, we'll have to continue // Channel ID was not updated when it was renamed to marketing. Thus, we'll have to continue
// to use this ID as the marketing channel ID // to use this ID as the marketing channel ID
private const val MARKETING_CHANNEL_ID = "org.mozilla.fenix.default.browser.channel" const val MARKETING_CHANNEL_ID = "org.mozilla.fenix.default.browser.channel"
// For notification that uses the marketing notification channel, IDs should be unique. // For notification that uses the marketing notification channel, IDs should be unique.
const val DEFAULT_BROWSER_NOTIFICATION_ID = 1 const val DEFAULT_BROWSER_NOTIFICATION_ID = 1
@ -26,7 +24,6 @@ const val RE_ENGAGEMENT_NOTIFICATION_ID = 2
* Returns the channel id to be used for notifications. * Returns the channel id to be used for notifications.
*/ */
fun ensureMarketingChannelExists(context: Context): String { fun ensureMarketingChannelExists(context: Context): String {
var channelEnabled = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager: NotificationManager = val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@ -43,18 +40,7 @@ fun ensureMarketingChannelExists(context: Context): String {
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
} }
channelEnabled = channel.importance != NotificationManager.IMPORTANCE_NONE
}
@Suppress("TooGenericExceptionCaught")
val notificationsEnabled = try {
NotificationManagerCompat.from(context).areNotificationsEnabled()
} catch (e: Exception) {
false
} }
marketingNotificationAllowed.set(notificationsEnabled && channelEnabled)
return MARKETING_CHANNEL_ID return MARKETING_CHANNEL_ID
} }

@ -0,0 +1,233 @@
/* 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.onboarding.view
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.accompanist.insets.navigationBarsPadding
import com.google.accompanist.insets.statusBarsPadding
import mozilla.components.service.glean.private.NoExtras
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.button.PrimaryButton
import org.mozilla.fenix.compose.button.SecondaryButton
import org.mozilla.fenix.theme.FirefoxTheme
/**
* Model containing data for the [NotificationPermissionPage].
*
* @param image [DrawableRes] displayed on the page.
* @param title [StringRes] of the permission headline text.
* @param description [StringRes] of the permission body text.
* @param primaryButtonText [StringRes] of the primary button text.
* @param secondaryButtonText [StringRes] of the secondary button text.
* @param onRecordImpressionEvent Callback for recording impression event.
*/
private data class NotificationPermissionPageState(
@DrawableRes val image: Int,
@StringRes val title: Int,
@StringRes val description: Int,
@StringRes val primaryButtonText: Int,
@StringRes val secondaryButtonText: Int? = null,
val onRecordImpressionEvent: () -> Unit,
)
/**
* A screen for displaying notification pre permission prompt.
*
* @param onDismiss Invoked when the user clicks on the close or the negative button.
* @param grantNotificationPermission Invoked when the user clicks on the positive button.
*/
@Composable
fun NotificationPermissionDialogScreen(
onDismiss: () -> Unit,
grantNotificationPermission: () -> Unit,
) {
NotificationPermissionContent(
notificationPermissionPageState = NotificationPageState,
onDismiss = {
onDismiss()
Onboarding.notifPppCloseClick.record(NoExtras())
},
onPrimaryButtonClick = {
grantNotificationPermission()
Onboarding.notifPppPositiveBtnClick.record(NoExtras())
},
onSecondaryButtonClick = {
onDismiss()
Onboarding.notifPppNegativeBtnClick.record(NoExtras())
},
)
}
@Composable
private fun NotificationPermissionContent(
notificationPermissionPageState: NotificationPermissionPageState,
onDismiss: () -> Unit,
onPrimaryButtonClick: () -> Unit,
onSecondaryButtonClick: () -> Unit,
modifier: Modifier = Modifier,
) {
BoxWithConstraints(Modifier.fillMaxSize()) {
val boxWithConstraintsScope = this
Column(
modifier = modifier
.background(FirefoxTheme.colors.layer1)
.fillMaxSize()
.padding(bottom = 32.dp)
.statusBarsPadding()
.navigationBarsPadding(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
IconButton(
onClick = onDismiss,
modifier = Modifier.align(Alignment.End),
) {
Icon(
painter = painterResource(id = R.drawable.mozac_ic_close),
contentDescription = stringResource(R.string.content_description_close_button),
tint = FirefoxTheme.colors.iconPrimary,
)
}
NotificationPermissionPage(
pageState = notificationPermissionPageState,
onPrimaryButtonClick = onPrimaryButtonClick,
onSecondaryButtonClick = onSecondaryButtonClick,
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
imageModifier = Modifier
.height(boxWithConstraintsScope.maxHeight.times(IMAGE_HEIGHT_RATIO)),
)
}
}
}
/**
* A page for displaying Notification Permission Content.
*
* @param pageState The page content that's displayed.
* @param onPrimaryButtonClick Invoked when the user clicks the primary button.
* @param onSecondaryButtonClick Invoked when the user clicks the secondary button.
* @param modifier The modifier to be applied to the Composable.
*/
@Composable
private fun NotificationPermissionPage(
pageState: NotificationPermissionPageState,
onPrimaryButtonClick: () -> Unit,
onSecondaryButtonClick: () -> Unit,
modifier: Modifier = Modifier,
imageModifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly,
) {
Image(
painter = painterResource(id = pageState.image),
contentDescription = null,
modifier = imageModifier,
)
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(
id = pageState.title,
formatArgs = arrayOf(stringResource(R.string.app_name)),
),
color = FirefoxTheme.colors.textPrimary,
textAlign = TextAlign.Center,
style = FirefoxTheme.typography.headline5,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(
id = pageState.description,
formatArgs = arrayOf(stringResource(R.string.app_name)),
),
color = FirefoxTheme.colors.textSecondary,
textAlign = TextAlign.Center,
style = FirefoxTheme.typography.body2,
)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(top = 16.dp),
) {
PrimaryButton(
text = stringResource(id = pageState.primaryButtonText),
onClick = onPrimaryButtonClick,
)
if (pageState.secondaryButtonText != null) {
Spacer(modifier = Modifier.height(8.dp))
SecondaryButton(
text = stringResource(id = pageState.secondaryButtonText),
onClick = onSecondaryButtonClick,
)
}
}
}
LaunchedEffect(pageState) {
pageState.onRecordImpressionEvent()
}
}
private val NotificationPageState = NotificationPermissionPageState(
image = R.drawable.ic_notification_permission,
title = R.string.onboarding_home_enable_notifications_title,
description = R.string.onboarding_home_enable_notifications_description,
primaryButtonText = R.string.onboarding_home_enable_notifications_positive_button,
secondaryButtonText = R.string.onboarding_home_enable_notifications_negative_button,
onRecordImpressionEvent = { Onboarding.notifPppImpression.record(NoExtras()) },
)
private const val IMAGE_HEIGHT_RATIO = 0.4f
@Preview
@Composable
private fun NotificationPermissionScreenPreview() {
FirefoxTheme {
NotificationPermissionDialogScreen(
grantNotificationPermission = {},
onDismiss = { },
)
}
}

@ -164,7 +164,7 @@ private fun OnboardingWelcomeBottomContent(
@Composable @Composable
private fun OnboardingWelcomeContent() { private fun OnboardingWelcomeContent() {
Column( Column(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp, vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Image( Image(
@ -195,7 +195,7 @@ private fun OnboardingWelcomeContent() {
@Composable @Composable
private fun OnboardingSyncSignInContent() { private fun OnboardingSyncSignInContent() {
Column( Column(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp, vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Image( Image(

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

Loading…
Cancel
Save