Skip to content

Commit 8add0a9

Browse files
Intercept sync setup URLs that are passed into our app (#6008)
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1209949135451037?focus=true ### Description Adds the ability for our app to consume sync pairing URLs. For context, there are 3 main scenarios involving scanning the sync barcodes: - the user scanned using our in-app scanner - the user scanned using their normal camera app, and another browser is set as default - the user scanned using their normal camera app, and our app is the default browser. - 👆 **this flow is the relevant one** that this PR adds support for. When the user scans the sync setup barcode using their system camera app and it's recognised as a URL, our app will be handed the `intent` for the URL. We identify it as a sync setup URL and: - prevent it from launching inside a custom tab - prevent it from loading as a normal webview load in a tab - launch the sync setup flow The sync setup flow begins with a prompt to ensure the user is intending to set up syncing with another device. <img src="https://github.com/user-attachments/assets/d20d0324-cd34-431c-84a3-cf24a4f0af44" width="30%" /> ### Steps to test this PR #### Scanning a `connect` code You'll need two devices, one of which is a physical one (to scan barcodes). Will call these "physical device" and "other device" in the following steps: - [x] Fresh install on both devices, and set us up as the default browser on the physical device - [x] On the other device, go to sync settings and choose to `Sync With Another Device` (`connect` code) - [x] On the physical device, launch the system camera app. It should recognise it as a URL and let you tap to launch it. - [x] Verify it launches into us prompting you to confirm you want to `Sync new device?` - [x] Choose `Cancel`, and verify you're left on the `Sync Settings` page - [x] Scan it again from the system camera app and this time confirm you do want to sync with the new device - [x] Verify you see the "setting up" screen and that sync successfully is set up #### Scanning an `exchange` code - [x] Log out on the physical device; stay logged into sync on the other device - [x] On the other device, go to sync settings and choose to `Sync With Another Device` (`exchange` code) - [x] On the physical device, launch the system camera app. It should recognise it as a URL and let you tap to launch it. - [x] Verify it launches into us prompting you to confirm you want to `Sync new device?`. Say yes. - [x] Verify you see the "setting up" screen and that sync successfully is set up #### Transparent sync account switching - [x] Log out on the physical device; stay logged into sync on the other device - [x] On the physical device, choose `Sync and Back Up This Device` and complete that flow. They are now on separate sync accounts. - [x] On the other device, go to sync settings and choose to `Sync With Another Device` - [x] On the physical device, launch the system camera app. It should recognise it as a URL and let you tap to launch it. - [x] Verify it launches into us prompting you to confirm you want to `Sync new device?`. Say yes. - [x] Verify you see the "setting up" screen and that sync successfully is set up #### Prompted sync account switching You'll need a third device this time. We'll imaginatively call this one "third device". - [x] Remain logged into the same account on physical device and other device (which is the state you'll be in from last test). - [x] On the third device, set up sync on its own account. - so physical + other are connected; the third device is logged into a separate sync account by itself - [x] On the third device, go to sync settings and choose to `Sync With Another Device` - [x] On the physical device, launch the system camera app. It should recognise it as a URL and let you tap to launch it. - [x] Verify it launches into us prompting you to confirm you want to `Sync new device?`. Say yes. - [ ] Verify you see the "setting up" screen - [x] Verify you are prompted to switch accounts. Agree, and verify sync completes and you're switched over so that physical and third device are now connected - [x] Repeat the process but say no when prompted; verify you are returned to the sync setup screen and your account wasn't switched. #### Other device disappears during exchange flow - [x] Log out on the physical device; stay logged into sync on the other device - [x] On the other device, go to sync settings and choose to `Sync With Another Device` (`exchange` code) - [x] On the physical device, launch the system camera app. It should recognise it as a URL and let you tap to launch it. - [x] Verify it launches into us prompting you to confirm you want to `Sync new device?`. **Don't say yes, yet.** - [x] On the other device, close the barcode screen - [x] Now, on physical device, say yes to syncing that other device. - [x] Verify you see the "setting up" screen. But this can't succeed, so verify after some time you see an error message. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210205485995728 --------- Co-authored-by: Craig Russell <[email protected]> Co-authored-by: Cristian Monforte <[email protected]>
1 parent b65a744 commit 8add0a9

File tree

64 files changed

+1398
-270
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1398
-270
lines changed

app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import androidx.activity.result.contract.ActivityResultContracts
3535
import androidx.annotation.VisibleForTesting
3636
import androidx.core.view.isVisible
3737
import androidx.core.view.postDelayed
38-
import androidx.fragment.app.Fragment
3938
import androidx.lifecycle.Lifecycle.State.STARTED
4039
import androidx.lifecycle.lifecycleScope
4140
import androidx.lifecycle.repeatOnLifecycle
@@ -103,6 +102,8 @@ import com.duckduckgo.duckchat.api.DuckChat
103102
import com.duckduckgo.navigation.api.GlobalActivityStarter
104103
import com.duckduckgo.savedsites.impl.bookmarks.BookmarksActivity.Companion.SAVED_SITE_URL_EXTRA
105104
import com.duckduckgo.site.permissions.impl.ui.SitePermissionScreenNoParams
105+
import com.duckduckgo.sync.api.SyncActivityFromSetupUrl
106+
import com.duckduckgo.sync.api.setup.SyncUrlIdentifier
106107
import javax.inject.Inject
107108
import kotlinx.coroutines.CoroutineScope
108109
import kotlinx.coroutines.Job
@@ -177,6 +178,9 @@ open class BrowserActivity : DuckDuckGoActivity() {
177178
@Inject
178179
lateinit var duckChat: DuckChat
179180

181+
@Inject
182+
lateinit var syncUrlIdentifier: SyncUrlIdentifier
183+
180184
@Inject
181185
lateinit var visualDesignExperimentDataStore: VisualDesignExperimentDataStore
182186

@@ -523,6 +527,14 @@ open class BrowserActivity : DuckDuckGoActivity() {
523527
return
524528
}
525529

530+
intent.intentText?.let {
531+
if (syncUrlIdentifier.shouldDelegateToSyncSetup(it)) {
532+
globalActivityStarter.start(this, SyncActivityFromSetupUrl(it))
533+
logcat { "Sync setup link was consumed, so don't allow it to open in a new tab" }
534+
return
535+
}
536+
}
537+
526538
// the BrowserActivity will automatically clear its stack of activities when being brought to the foreground, so this can no longer be true
527539
currentTab?.inContextEmailProtectionShowing = false
528540

@@ -1020,7 +1032,11 @@ open class BrowserActivity : DuckDuckGoActivity() {
10201032
tabPagerAdapter.onTabsUpdated(updatedTabIds)
10211033
}
10221034

1023-
fun launchNewTab(query: String? = null, sourceTabId: String? = null, skipHome: Boolean = false) {
1035+
fun launchNewTab(
1036+
query: String? = null,
1037+
sourceTabId: String? = null,
1038+
skipHome: Boolean = false,
1039+
) {
10241040
lifecycleScope.launch {
10251041
if (swipingTabsFeature.isEnabled) {
10261042
tabManager.openNewTab(query, sourceTabId, skipHome)

app/src/main/java/com/duckduckgo/app/dispatchers/IntentDispatcherViewModel.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.duckduckgo.autofill.api.emailprotection.EmailProtectionLinkVerifier
2727
import com.duckduckgo.common.utils.DispatcherProvider
2828
import com.duckduckgo.customtabs.api.CustomTabDetector
2929
import com.duckduckgo.di.scopes.ActivityScope
30+
import com.duckduckgo.sync.api.setup.SyncUrlIdentifier
3031
import javax.inject.Inject
3132
import kotlinx.coroutines.flow.MutableStateFlow
3233
import kotlinx.coroutines.flow.asStateFlow
@@ -40,6 +41,7 @@ class IntentDispatcherViewModel @Inject constructor(
4041
private val dispatcherProvider: DispatcherProvider,
4142
private val emailProtectionLinkVerifier: EmailProtectionLinkVerifier,
4243
private val duckDuckGoUrlDetector: DuckDuckGoUrlDetector,
44+
private val syncUrlIdentifier: SyncUrlIdentifier,
4345
) : ViewModel() {
4446

4547
private val _viewState = MutableStateFlow(ViewState())
@@ -60,7 +62,9 @@ class IntentDispatcherViewModel @Inject constructor(
6062
val toolbarColor = intent?.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR, defaultColor) ?: defaultColor
6163
val isEmailProtectionLink = emailProtectionLinkVerifier.shouldDelegateToInContextView(intentText, true)
6264
val isDuckDuckGoUrl = intentText?.let { duckDuckGoUrlDetector.isDuckDuckGoUrl(it) } ?: false
63-
val customTabRequested = hasSession && !isEmailProtectionLink && !isDuckDuckGoUrl
65+
66+
val isSyncPairingUrl = syncUrlIdentifier.shouldDelegateToSyncSetup(intentText)
67+
val customTabRequested = hasSession && !isEmailProtectionLink && !isDuckDuckGoUrl && !isSyncPairingUrl
6468

6569
logcat { "Intent $intent received. Has extra session=$hasSession. Intent text=$intentText. Toolbar color=$toolbarColor" }
6670

app/src/test/java/com/duckduckgo/app/dispatchers/IntentDispatcherViewModelTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import com.duckduckgo.app.global.intentText
2424
import com.duckduckgo.autofill.api.emailprotection.EmailProtectionLinkVerifier
2525
import com.duckduckgo.common.test.CoroutineTestRule
2626
import com.duckduckgo.customtabs.api.CustomTabDetector
27+
import com.duckduckgo.sync.api.setup.SyncUrlIdentifier
28+
import com.duckduckgo.sync.impl.ui.qrcode.SyncBarcodeUrl
2729
import kotlinx.coroutines.test.runTest
2830
import org.junit.Assert.assertEquals
2931
import org.junit.Assert.assertFalse
@@ -44,6 +46,7 @@ class IntentDispatcherViewModelTest {
4446
private val mockIntent: Intent = mock()
4547
private val emailProtectionLinkVerifier: EmailProtectionLinkVerifier = mock()
4648
private val duckDuckGoUrlDetector: DuckDuckGoUrlDetector = mock()
49+
private val syncUrlIdentifier: SyncUrlIdentifier = mock()
4750

4851
private lateinit var testee: IntentDispatcherViewModel
4952

@@ -54,7 +57,10 @@ class IntentDispatcherViewModelTest {
5457
dispatcherProvider = coroutineTestRule.testDispatcherProvider,
5558
emailProtectionLinkVerifier = emailProtectionLinkVerifier,
5659
duckDuckGoUrlDetector = duckDuckGoUrlDetector,
60+
syncUrlIdentifier = syncUrlIdentifier,
5761
)
62+
63+
whenever(syncUrlIdentifier.shouldDelegateToSyncSetup(anyOrNull())).thenReturn(false)
5864
}
5965

6066
@Test
@@ -193,6 +199,20 @@ class IntentDispatcherViewModelTest {
193199
}
194200
}
195201

202+
@Test
203+
fun whenIntentReceivedForSyncPairingUrlThenCustomTabIsNotRequested() = runTest {
204+
val intentUrl = SyncBarcodeUrl.URL_BASE
205+
whenever(mockIntent.intentText).thenReturn(intentUrl)
206+
whenever(syncUrlIdentifier.shouldDelegateToSyncSetup(intentUrl)).thenReturn(true)
207+
testee.onIntentReceived(mockIntent, DEFAULT_COLOR, isExternal = true)
208+
209+
testee.viewState.test {
210+
val state = awaitItem()
211+
assertFalse(state.customTabRequested)
212+
assertEquals(intentUrl, state.intentText)
213+
}
214+
}
215+
196216
private fun configureHasSession(returnValue: Boolean) {
197217
whenever(mockIntent.hasExtra(CustomTabsIntent.EXTRA_SESSION)).thenReturn(returnValue)
198218
}

sync/sync-api/src/main/java/com/duckduckgo/sync/api/SyncScreens.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,14 @@ import com.duckduckgo.navigation.api.GlobalActivityStarter
2525
* ```
2626
*/
2727
object SyncActivityWithEmptyParams : GlobalActivityStarter.ActivityParams
28+
29+
/**
30+
* Use this class to launch the sync screen with a URL-based sync pairing code
31+
* It is not expected that the URL would be hand-crafted
32+
* This is to support the flow when a URL-based sync setup QR code is scanned using the normal camera app and we're the default browser
33+
*
34+
* ```kotlin
35+
* globalActivityStarter.start(context, SyncActivityFromSetupUrl("https://duckduckgo.com/sync/pairing/#&code=ABC-123&deviceName=iPhone"))
36+
* ```
37+
*/
38+
data class SyncActivityFromSetupUrl(val url: String) : GlobalActivityStarter.ActivityParams
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.sync.api.setup
18+
19+
/**
20+
* Used to determine if the app was launched with an intent that should be delegated to sync setup flow
21+
*/
22+
interface SyncUrlIdentifier {
23+
24+
fun shouldDelegateToSyncSetup(intentText: String?): Boolean
25+
}

sync/sync-impl/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ plugins {
1818
id 'com.android.library'
1919
id 'kotlin-android'
2020
id 'com.squareup.anvil'
21+
id 'kotlin-parcelize'
2122
}
2223

2324
apply from: "$rootProject.projectDir/gradle/android-library.gradle"

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,10 @@ interface SyncFeature {
5959

6060
@Toggle.DefaultValue(DefaultFeatureValue.TRUE)
6161
fun canScanUrlBasedSyncSetupBarcodes(): Toggle
62+
63+
@Toggle.DefaultValue(DefaultFeatureValue.TRUE)
64+
fun canInterceptSyncSetupUrls(): Toggle
65+
66+
@Toggle.DefaultValue(DefaultFeatureValue.TRUE)
67+
fun canOverrideThemeSyncSetup(): Toggle
6268
}

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/pixels/SyncPixelParamRemovalPlugin.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ class SyncPixelParamRemovalPlugin @Inject constructor() : PixelParamRemovalPlugi
4242
SyncPixelName.SYNC_USER_SWITCHED_ACCOUNT.pixelName to PixelParameter.removeAtb(),
4343
SyncPixelName.SYNC_USER_SWITCHED_LOGOUT_ERROR.pixelName to PixelParameter.removeAtb(),
4444
SyncPixelName.SYNC_USER_SWITCHED_LOGIN_ERROR.pixelName to PixelParameter.removeAtb(),
45+
46+
SyncPixelName.SYNC_SETUP_DEEP_LINK_TIMEOUT.pixelName to PixelParameter.removeAtb(),
47+
SyncPixelName.SYNC_SETUP_DEEP_LINK_FLOW_STARTED.pixelName to PixelParameter.removeAtb(),
48+
SyncPixelName.SYNC_SETUP_DEEP_LINK_FLOW_SUCCESS.pixelName to PixelParameter.removeAtb(),
49+
SyncPixelName.SYNC_SETUP_DEEP_LINK_FLOW_ABANDONED.pixelName to PixelParameter.removeAtb(),
50+
51+
SyncPixelName.SYNC_SETUP_BARCODE_SCREEN_SHOWN.pixelName to PixelParameter.removeAtb(),
52+
SyncPixelName.SYNC_SETUP_BARCODE_SCANNER_SUCCESS.pixelName to PixelParameter.removeAtb(),
53+
SyncPixelName.SYNC_SETUP_BARCODE_SCANNER_FAILED.pixelName to PixelParameter.removeAtb(),
54+
SyncPixelName.SYNC_SETUP_BARCODE_CODE_COPIED.pixelName to PixelParameter.removeAtb(),
55+
SyncPixelName.SYNC_SETUP_MANUAL_CODE_ENTRY_SCREEN_SHOWN.pixelName to PixelParameter.removeAtb(),
56+
SyncPixelName.SYNC_SETUP_MANUAL_CODE_ENTERED_FAILED.pixelName to PixelParameter.removeAtb(),
57+
SyncPixelName.SYNC_SETUP_MANUAL_CODE_ENTERED_SUCCESS.pixelName to PixelParameter.removeAtb(),
58+
SyncPixelName.SYNC_SETUP_ENDED_ABANDONED.pixelName to PixelParameter.removeAtb(),
59+
SyncPixelName.SYNC_SETUP_ENDED_SUCCESS.pixelName to PixelParameter.removeAtb(),
4560
)
4661
}
4762
}

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/pixels/SyncPixels.kt

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import com.duckduckgo.sync.impl.pixels.SyncPixelName.SYNC_DAILY
2727
import com.duckduckgo.sync.impl.pixels.SyncPixelName.SYNC_DAILY_SUCCESS_RATE_PIXEL
2828
import com.duckduckgo.sync.impl.pixels.SyncPixelName.SYNC_OBJECT_LIMIT_EXCEEDED_DAILY
2929
import com.duckduckgo.sync.impl.pixels.SyncPixelParameters.SYNC_FEATURE_PROMOTION_SOURCE
30+
import com.duckduckgo.sync.impl.pixels.SyncPixelParameters.SYNC_SETUP_SCREEN_TYPE
31+
import com.duckduckgo.sync.impl.pixels.SyncPixels.ScreenType
3032
import com.duckduckgo.sync.impl.stats.SyncStatsRepository
3133
import com.duckduckgo.sync.store.SharedPrefsProvider
3234
import com.squareup.anvil.annotations.ContributesBinding
@@ -88,6 +90,24 @@ interface SyncPixels {
8890
fun fireUserSwitchedAccount()
8991
fun fireUserSwitchedLogoutError()
9092
fun fireUserSwitchedLoginError()
93+
fun fireTimeoutOnDeepLinkSetup()
94+
fun fireSyncBarcodeScreenShown(screenType: ScreenType)
95+
fun fireSyncSetupFinishedSuccessfully(screenType: ScreenType)
96+
fun fireSyncSetupAbandoned(screenType: ScreenType)
97+
fun fireSyncSetupManualCodeScreenShown(screenType: ScreenType)
98+
fun fireSyncSetupCodePastedParseSuccess(screenType: ScreenType)
99+
fun fireSyncSetupCodePastedParseFailure(screenType: ScreenType)
100+
fun fireSyncSetupCodeCopiedToClipboard(screenType: ScreenType)
101+
fun fireBarcodeScannerParseError(screenType: ScreenType)
102+
fun fireBarcodeScannerParseSuccess(screenType: ScreenType)
103+
104+
enum class ScreenType(val value: String) {
105+
SYNC_CONNECT("connect"),
106+
SYNC_EXCHANGE("exchange"),
107+
}
108+
fun fireSetupDeepLinkFlowStarted()
109+
fun fireSetupDeepLinkFlowSuccess()
110+
fun fireSetupDeepLinkFlowAbandoned()
91111
}
92112

93113
@ContributesBinding(AppScope::class)
@@ -282,10 +302,71 @@ class RealSyncPixels @Inject constructor(
282302
pixel.fire(SyncPixelName.SYNC_USER_SWITCHED_LOGIN_ERROR)
283303
}
284304

305+
override fun fireTimeoutOnDeepLinkSetup() {
306+
pixel.fire(SyncPixelName.SYNC_SETUP_DEEP_LINK_TIMEOUT)
307+
}
308+
285309
override fun fireUserSwitchedLogoutError() {
286310
pixel.fire(SyncPixelName.SYNC_USER_SWITCHED_LOGOUT_ERROR)
287311
}
288312

313+
override fun fireSetupDeepLinkFlowStarted() {
314+
pixel.fire(SyncPixelName.SYNC_SETUP_DEEP_LINK_FLOW_STARTED)
315+
}
316+
317+
override fun fireSetupDeepLinkFlowSuccess() {
318+
pixel.fire(SyncPixelName.SYNC_SETUP_DEEP_LINK_FLOW_SUCCESS)
319+
}
320+
321+
override fun fireSetupDeepLinkFlowAbandoned() {
322+
pixel.fire(SyncPixelName.SYNC_SETUP_DEEP_LINK_FLOW_ABANDONED)
323+
}
324+
325+
override fun fireSyncBarcodeScreenShown(screenType: ScreenType) {
326+
val params = mapOf(SYNC_SETUP_SCREEN_TYPE to screenType.value)
327+
pixel.fire(SyncPixelName.SYNC_SETUP_BARCODE_SCREEN_SHOWN, parameters = params)
328+
}
329+
330+
override fun fireSyncSetupAbandoned(screenType: ScreenType) {
331+
val params = mapOf(SYNC_SETUP_SCREEN_TYPE to screenType.value)
332+
pixel.fire(SyncPixelName.SYNC_SETUP_ENDED_ABANDONED, parameters = params)
333+
}
334+
335+
override fun fireSyncSetupFinishedSuccessfully(screenType: ScreenType) {
336+
val params = mapOf(SYNC_SETUP_SCREEN_TYPE to screenType.value)
337+
pixel.fire(SyncPixelName.SYNC_SETUP_ENDED_SUCCESS, parameters = params)
338+
}
339+
340+
override fun fireSyncSetupManualCodeScreenShown(screenType: ScreenType) {
341+
val params = mapOf(SYNC_SETUP_SCREEN_TYPE to screenType.value)
342+
pixel.fire(SyncPixelName.SYNC_SETUP_MANUAL_CODE_ENTRY_SCREEN_SHOWN, parameters = params)
343+
}
344+
345+
override fun fireSyncSetupCodePastedParseSuccess(screenType: ScreenType) {
346+
val params = mapOf(SYNC_SETUP_SCREEN_TYPE to screenType.value)
347+
pixel.fire(SyncPixelName.SYNC_SETUP_MANUAL_CODE_ENTERED_SUCCESS, parameters = params)
348+
}
349+
350+
override fun fireSyncSetupCodePastedParseFailure(screenType: ScreenType) {
351+
val params = mapOf(SYNC_SETUP_SCREEN_TYPE to screenType.value)
352+
pixel.fire(SyncPixelName.SYNC_SETUP_MANUAL_CODE_ENTERED_FAILED, parameters = params)
353+
}
354+
355+
override fun fireSyncSetupCodeCopiedToClipboard(screenType: ScreenType) {
356+
val params = mapOf(SYNC_SETUP_SCREEN_TYPE to screenType.value)
357+
pixel.fire(SyncPixelName.SYNC_SETUP_BARCODE_CODE_COPIED, parameters = params)
358+
}
359+
360+
override fun fireBarcodeScannerParseSuccess(screenType: ScreenType) {
361+
val params = mapOf(SYNC_SETUP_SCREEN_TYPE to screenType.value)
362+
pixel.fire(SyncPixelName.SYNC_SETUP_BARCODE_SCANNER_SUCCESS, parameters = params)
363+
}
364+
365+
override fun fireBarcodeScannerParseError(screenType: ScreenType) {
366+
val params = mapOf(SYNC_SETUP_SCREEN_TYPE to screenType.value)
367+
pixel.fire(SyncPixelName.SYNC_SETUP_BARCODE_SCANNER_FAILED, parameters = params)
368+
}
369+
289370
companion object {
290371
private const val SYNC_PIXELS_PREF_FILE = "com.duckduckgo.sync.pixels.v1"
291372
}
@@ -339,6 +420,20 @@ enum class SyncPixelName(override val pixelName: String) : Pixel.PixelName {
339420
SYNC_USER_SWITCHED_ACCOUNT("sync_user_switched_account"),
340421
SYNC_USER_SWITCHED_LOGOUT_ERROR("sync_user_switched_logout_error"),
341422
SYNC_USER_SWITCHED_LOGIN_ERROR("sync_user_switched_login_error"),
423+
SYNC_SETUP_DEEP_LINK_TIMEOUT("sync_setup_deep_link_timeout"),
424+
SYNC_SETUP_DEEP_LINK_FLOW_STARTED("sync_setup_deep_link_flow_started"),
425+
SYNC_SETUP_DEEP_LINK_FLOW_SUCCESS("sync_setup_deep_link_flow_success"),
426+
SYNC_SETUP_DEEP_LINK_FLOW_ABANDONED("sync_setup_deep_link_flow_abandoned"),
427+
428+
SYNC_SETUP_BARCODE_SCREEN_SHOWN("sync_setup_barcode_screen_shown"),
429+
SYNC_SETUP_BARCODE_SCANNER_SUCCESS("sync_setup_barcode_scanner_success"),
430+
SYNC_SETUP_BARCODE_SCANNER_FAILED("sync_setup_barcode_scanner_failed"),
431+
SYNC_SETUP_BARCODE_CODE_COPIED("sync_setup_barcode_code_copied"),
432+
SYNC_SETUP_MANUAL_CODE_ENTRY_SCREEN_SHOWN("sync_setup_manual_code_entry_screen_shown"),
433+
SYNC_SETUP_MANUAL_CODE_ENTERED_SUCCESS("sync_setup_manual_code_entered_success"),
434+
SYNC_SETUP_MANUAL_CODE_ENTERED_FAILED("sync_setup_manual_code_entered_failed"),
435+
SYNC_SETUP_ENDED_ABANDONED("sync_setup_ended_abandoned"),
436+
SYNC_SETUP_ENDED_SUCCESS("sync_setup_ended_success"),
342437
}
343438

344439
object SyncPixelParameters {
@@ -359,4 +454,5 @@ object SyncPixelParameters {
359454
const val ERROR = "error"
360455
const val SYNC_FEATURE_PROMOTION_SOURCE = "source"
361456
const val GET_OTHER_DEVICES_SCREEN_LAUNCH_SOURCE = "source"
457+
const val SYNC_SETUP_SCREEN_TYPE = "source"
362458
}

sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/promotion/SyncGetOnOtherPlatformsActivity.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,14 @@ class SyncGetOnOtherPlatformsActivity : DuckDuckGoActivity() {
104104
}
105105

106106
private fun extractLaunchSource(): String? {
107-
return intent.getActivityParams(SyncGetOnOtherPlatformsParams::class.java)?.source
107+
return intent.getActivityParams(SyncGetOnOtherPlatformsParams::class.java)?.source?.value
108108
}
109109
}
110110

111-
data class SyncGetOnOtherPlatformsParams(val source: String?) : GlobalActivityStarter.ActivityParams
111+
data class SyncGetOnOtherPlatformsParams(val source: SyncGetOnOtherPlatformsLaunchSource) : GlobalActivityStarter.ActivityParams
112+
113+
enum class SyncGetOnOtherPlatformsLaunchSource(val value: String) {
114+
SOURCE_ACTIVATING("activating"),
115+
SOURCE_SYNC_DISABLED("not_activated"),
116+
SOURCE_SYNC_ENABLED("activated"),
117+
}

0 commit comments

Comments
 (0)