-
Notifications
You must be signed in to change notification settings - Fork 927
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract ad campaign from Play Store referrer (#4818)
Task/Issue URL: https://app.asana.com/0/488551667048375/1207902032895525/f ### Description Add ability to extract an ad campaign from Play Store referrer URLs. ### Steps to test this PR ℹ️ Logcat filter: `Pixel sent: m_android_install` ℹ️ This requires the Play Store to be available and active on your test device / emulator.⚠️ You need to remove the **production** app from your device during this test **Local changes for testing** it's a nightmare to E2E test this because it involves first having released these changes in an app version on the Play Store, so that the app you install from the Play Store referral link has the changes already in it, but we can't release until we've tested the changes 🐔/🥚 . there is a neat hack to simulate this workflow though - [x] delete the line `applicationIdSuffix ".debug"` from your `build.gradle` - [x] Uninstall the prod app if already installed **Send a Play Store app install link containing referrer to your test device / emulator** - [x] Send a URL to your device so that you can click on it (e.g., via email). The format should be like this: - ```https://play.google.com/store/apps/details?id=com.duckduckgo.mobile.android&referrer=origin%3Dfunnel_playstore_whatever``` - You can change the value to test different things, including adding extra parts afterwards but it must start `origin%3D` - [x] Click on the link to launch the app's listing in the Play Store app. **Don't install it** **Install the APK from this branch (Play variant)** - [x] Now you can install the APK from this branch `./gradlew installPlayDebug` - [x] Launch the app - [x] Verify in logcat that: install pixel contains ad campaign you specified in the URL above - [x] Verify pixel params look right, including `reinstall` value - [x] Close the app and relaunch; verify not sent again **Testing what happens if referrer does not contain origin (when not installed from Play Store)** - [x] Delete the app - [x] Install from this branch `./gradlew installPlayDebug` (without visiting the referral link) and launch the app - [x] Verify in logcat that: install pixel still fires but that there is no origin attached **Testing what happens if referrer does not contain origin (when installed from Play Store)** - [x] Requires local hack: hardcode `VerificationCheckPlayStoreInstallImpl.installedFromPlayStore()` to return `true` - [x] Delete the app - [x] Install from this branch `./gradlew installPlayDebug` (without visiting the referral link) and launch the app - [x] Verify in logcat that: install pixel still fires and that origin has been set to default, `origin=funnel_playstore` **Testing reinstall flag picked up correctly** - [x] Requires local hack: hardcode `ReinstallAtbListener.beforeAtbInit()` to detect returning user - [x] Delete the app - [x] `./gradlew installPlayDebug` (don't need to visit the referral link) and launch the app - [x] Verify in logcat that: install pixel fires and that `reinstall=true` - [x] Repeat the above but hardcoding returning user to false, and verify `reinstall=false` **Testing non-Play variant** - [x] Delete app - [x] `./gradlew installInternalDebug` and launch app - [x] Verify in logcat that: install pixel **not** fired
- Loading branch information
Showing
12 changed files
with
460 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
app/src/main/java/com/duckduckgo/app/referral/ReferrerOriginAttributeHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* Copyright (c) 2024 DuckDuckGo | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.duckduckgo.app.referral | ||
|
||
import com.duckduckgo.di.scopes.AppScope | ||
import com.duckduckgo.verifiedinstallation.installsource.VerificationCheckPlayStoreInstall | ||
import com.squareup.anvil.annotations.ContributesBinding | ||
import javax.inject.Inject | ||
import timber.log.Timber | ||
|
||
interface ReferrerOriginAttributeHandler { | ||
fun process(referrerParts: List<String>) | ||
} | ||
|
||
@ContributesBinding(AppScope::class) | ||
class ReferrerOriginAttributeHandlerImpl @Inject constructor( | ||
private val appReferrerDataStore: AppReferrerDataStore, | ||
private val playStoreInstallChecker: VerificationCheckPlayStoreInstall, | ||
) : ReferrerOriginAttributeHandler { | ||
|
||
override fun process(referrerParts: List<String>) { | ||
runCatching { | ||
Timber.v("Looking for origin attribute referrer data") | ||
var originAttributePart = extractOriginAttribute(referrerParts) | ||
|
||
if (originAttributePart == null && playStoreInstallChecker.installedFromPlayStore()) { | ||
Timber.v("No origin attribute referrer data available; assigning one") | ||
originAttributePart = DEFAULT_ATTRIBUTION_FOR_PLAY_STORE_INSTALLS | ||
} | ||
|
||
persistOriginAttribute(originAttributePart) | ||
} | ||
} | ||
|
||
private fun extractOriginAttribute(referrerParts: List<String>): String? { | ||
val originAttributePart = referrerParts.find { it.startsWith("$ORIGIN_ATTRIBUTE_KEY=") } | ||
if (originAttributePart == null) { | ||
Timber.v("Did not find referrer origin attribute key") | ||
return null | ||
} | ||
|
||
Timber.v("Found referrer origin attribute: %s", originAttributePart) | ||
|
||
return originAttributePart.removePrefix("$ORIGIN_ATTRIBUTE_KEY=").also { | ||
Timber.i("Found referrer origin attribute value: %s", it) | ||
} | ||
} | ||
|
||
private fun persistOriginAttribute(originAttributePart: String?) { | ||
appReferrerDataStore.utmOriginAttributeCampaign = originAttributePart | ||
} | ||
|
||
companion object { | ||
const val ORIGIN_ATTRIBUTE_KEY = "origin" | ||
const val DEFAULT_ATTRIBUTION_FOR_PLAY_STORE_INSTALLS = "funnel_playstore" | ||
} | ||
} |
83 changes: 83 additions & 0 deletions
83
app/src/play/java/com/duckduckgo/referral/AppReferrerInstallPixelSender.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* Copyright (c) 2024 DuckDuckGo | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.duckduckgo.referral | ||
|
||
import com.duckduckgo.app.di.AppCoroutineScope | ||
import com.duckduckgo.app.pixels.AppPixelName | ||
import com.duckduckgo.app.referral.AppReferrerDataStore | ||
import com.duckduckgo.app.statistics.api.AtbLifecyclePlugin | ||
import com.duckduckgo.app.statistics.pixels.Pixel | ||
import com.duckduckgo.app.statistics.store.StatisticsDataStore | ||
import com.duckduckgo.appbuildconfig.api.AppBuildConfig | ||
import com.duckduckgo.common.utils.DispatcherProvider | ||
import com.duckduckgo.di.scopes.AppScope | ||
import com.squareup.anvil.annotations.ContributesMultibinding | ||
import java.util.concurrent.atomic.AtomicBoolean | ||
import javax.inject.Inject | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.launch | ||
import timber.log.Timber | ||
|
||
@ContributesMultibinding(scope = AppScope::class) | ||
class AppReferrerInstallPixelSender @Inject constructor( | ||
private val appReferrerDataStore: AppReferrerDataStore, | ||
private val pixel: Pixel, | ||
@AppCoroutineScope private val appCoroutineScope: CoroutineScope, | ||
private val dispatchers: DispatcherProvider, | ||
private val appBuildConfig: AppBuildConfig, | ||
private val statisticsDataStore: StatisticsDataStore, | ||
) : AtbLifecyclePlugin { | ||
|
||
private val pixelSent = AtomicBoolean(false) | ||
|
||
override fun onAppAtbInitialized() { | ||
Timber.v("AppReferrerInstallPixelSender: onAppAtbInitialized") | ||
sendPixelIfUnsent() | ||
} | ||
|
||
private fun sendPixelIfUnsent() { | ||
if (pixelSent.compareAndSet(false, true)) { | ||
appCoroutineScope.launch(dispatchers.io()) { | ||
sendOriginAttribute(appReferrerDataStore.utmOriginAttributeCampaign) | ||
} | ||
} | ||
} | ||
|
||
private fun sendOriginAttribute(originAttribute: String?) { | ||
val returningUser = statisticsDataStore.variant == RETURNING_USER_VARIANT | ||
|
||
val params = mutableMapOf( | ||
PIXEL_PARAM_LOCALE to appBuildConfig.deviceLocale.toLanguageTag(), | ||
PIXEL_PARAM_RETURNING_USER to returningUser.toString(), | ||
) | ||
|
||
// if origin is null, pixel is sent with origin omitted | ||
if (originAttribute != null) { | ||
params[PIXEL_PARAM_ORIGIN] = originAttribute | ||
} | ||
|
||
pixel.fire(pixel = AppPixelName.REFERRAL_INSTALL_UTM_CAMPAIGN, type = Pixel.PixelType.UNIQUE, parameters = params) | ||
} | ||
|
||
companion object { | ||
private const val RETURNING_USER_VARIANT = "ru" | ||
|
||
const val PIXEL_PARAM_ORIGIN = "origin" | ||
const val PIXEL_PARAM_LOCALE = "locale" | ||
const val PIXEL_PARAM_RETURNING_USER = "reinstall" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.