-
Notifications
You must be signed in to change notification settings - Fork 929
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add feature flag for sending full installer package ID (#5075)
Task/Issue URL: https://app.asana.com/0/608920331025315/1208315780180367/f ### Description Defines a feature flag for how to handle full installer package IDs. ### Steps to test this PR **QA-optional** - no observable change of behaviour in this PR (branch above will cover full testing). If you do want to check: - [x] Hardcode remote config download to use `https://jsonblob.com/api/1289196933024178176` - [x] Fresh install. Launch app and wait for remote config to download. - Check the contents of the data store (e.g., using `Device Explorer`), which will live in `com.duckduckgo.mobile.android.debug/files/datastore/com.duckduckgo.installation.impl.installer.preferences_pb` - [x] Note, the contents of the file won't render nicely but you should be able to see example packages `a.b.c` and `d.e.f` in there. e.g., 👇 <img src="https://github.com/user-attachments/assets/46a60ae1-3f7c-406b-9f10-7c079b5312d8" width="50%" />
- Loading branch information
Showing
11 changed files
with
459 additions
and
0 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
47 changes: 47 additions & 0 deletions
47
...ation-impl/src/main/java/com/duckduckgo/installation/impl/installer/di/InstallerModule.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,47 @@ | ||
/* | ||
* 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.installation.impl.installer.di | ||
|
||
import android.content.Context | ||
import androidx.datastore.core.DataStore | ||
import androidx.datastore.preferences.core.Preferences | ||
import androidx.datastore.preferences.preferencesDataStore | ||
import com.duckduckgo.di.scopes.AppScope | ||
import com.squareup.anvil.annotations.ContributesTo | ||
import dagger.Module | ||
import dagger.Provides | ||
import dagger.SingleInstanceIn | ||
import javax.inject.Qualifier | ||
|
||
@Module | ||
@ContributesTo(AppScope::class) | ||
object InstallerModule { | ||
|
||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore( | ||
name = "com.duckduckgo.installation.impl.installer", | ||
) | ||
|
||
@Provides | ||
@SingleInstanceIn(AppScope::class) | ||
@InstallSourceFullPackageDataStore | ||
fun provideInstallSourceFullPackageDataStore(context: Context): DataStore<Preferences> { | ||
return context.dataStore | ||
} | ||
|
||
@Qualifier | ||
annotation class InstallSourceFullPackageDataStore | ||
} |
74 changes: 74 additions & 0 deletions
74
...a/com/duckduckgo/installation/impl/installer/fullpackage/InstallSourceFullPackageStore.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,74 @@ | ||
/* | ||
* 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.installation.impl.installer.fullpackage | ||
|
||
import androidx.datastore.core.DataStore | ||
import androidx.datastore.preferences.core.Preferences | ||
import androidx.datastore.preferences.core.edit | ||
import androidx.datastore.preferences.core.stringSetPreferencesKey | ||
import com.duckduckgo.common.utils.DispatcherProvider | ||
import com.duckduckgo.di.scopes.AppScope | ||
import com.duckduckgo.installation.impl.installer.di.InstallerModule.InstallSourceFullPackageDataStore | ||
import com.duckduckgo.installation.impl.installer.fullpackage.InstallSourceFullPackageStore.IncludedPackages | ||
import com.duckduckgo.installation.impl.installer.fullpackage.feature.InstallSourceFullPackageListJsonParser | ||
import com.squareup.anvil.annotations.ContributesBinding | ||
import dagger.SingleInstanceIn | ||
import javax.inject.Inject | ||
import kotlinx.coroutines.flow.firstOrNull | ||
import kotlinx.coroutines.flow.map | ||
import kotlinx.coroutines.withContext | ||
|
||
interface InstallSourceFullPackageStore { | ||
suspend fun updateInstallSourceFullPackages(json: String) | ||
suspend fun getInstallSourceFullPackages(): IncludedPackages | ||
|
||
data class IncludedPackages(val list: List<String> = emptyList()) { | ||
|
||
fun hasWildcard(): Boolean { | ||
return list.contains("*") | ||
} | ||
} | ||
} | ||
|
||
@ContributesBinding(AppScope::class, boundType = InstallSourceFullPackageStore::class) | ||
@SingleInstanceIn(AppScope::class) | ||
class InstallSourceFullPackageStoreImpl @Inject constructor( | ||
private val dispatchers: DispatcherProvider, | ||
private val jsonParser: InstallSourceFullPackageListJsonParser, | ||
@InstallSourceFullPackageDataStore private val dataStore: DataStore<Preferences>, | ||
) : InstallSourceFullPackageStore { | ||
|
||
override suspend fun updateInstallSourceFullPackages(json: String) { | ||
withContext(dispatchers.io()) { | ||
val includedPackages = jsonParser.parseJson(json) | ||
dataStore.edit { | ||
it[packageInstallersKey] = includedPackages.list.toSet() | ||
} | ||
} | ||
} | ||
|
||
override suspend fun getInstallSourceFullPackages(): IncludedPackages { | ||
return withContext(dispatchers.io()) { | ||
val packageInstallers = dataStore.data.map { it[packageInstallersKey] }.firstOrNull() | ||
return@withContext IncludedPackages(packageInstallers?.toList() ?: emptyList()) | ||
} | ||
} | ||
|
||
companion object { | ||
val packageInstallersKey = stringSetPreferencesKey("package_installers") | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
...duckgo/installation/impl/installer/fullpackage/feature/InstallSourceFullPackageFeature.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,69 @@ | ||
/* | ||
* 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.installation.impl.installer.fullpackage.feature | ||
|
||
import com.duckduckgo.anvil.annotations.ContributesRemoteFeature | ||
import com.duckduckgo.app.di.AppCoroutineScope | ||
import com.duckduckgo.common.utils.DispatcherProvider | ||
import com.duckduckgo.di.scopes.AppScope | ||
import com.duckduckgo.feature.toggles.api.FeatureSettings | ||
import com.duckduckgo.feature.toggles.api.RemoteFeatureStoreNamed | ||
import com.duckduckgo.feature.toggles.api.Toggle | ||
import com.duckduckgo.installation.impl.installer.fullpackage.InstallSourceFullPackageStore | ||
import com.squareup.anvil.annotations.ContributesBinding | ||
import javax.inject.Inject | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.launch | ||
|
||
@ContributesRemoteFeature( | ||
scope = AppScope::class, | ||
boundType = InstallSourceFullPackageFeature::class, | ||
featureName = "sendFullPackageInstallSource", | ||
settingsStore = InstallSourceFullPackageFeatureSettingsStore::class, | ||
) | ||
/** | ||
* This is the class that represents the feature flag for sending full installer package ID. | ||
* This can be used to specify which app-installer package IDs we'd match on to send a pixel. | ||
* A wildcard "*" can be used to match all package IDs. | ||
*/ | ||
interface InstallSourceFullPackageFeature { | ||
/** | ||
* @return `true` when the remote config has the global "sendFullPackageInstallSource" feature flag enabled | ||
* | ||
* If the remote feature is not present defaults to `false` | ||
*/ | ||
|
||
@Toggle.DefaultValue(false) | ||
fun self(): Toggle | ||
} | ||
|
||
@ContributesBinding(AppScope::class) | ||
@RemoteFeatureStoreNamed(InstallSourceFullPackageFeature::class) | ||
class InstallSourceFullPackageFeatureSettingsStore @Inject constructor( | ||
private val dataStore: InstallSourceFullPackageStore, | ||
private val dispatchers: DispatcherProvider, | ||
@AppCoroutineScope private val appCoroutineScope: CoroutineScope, | ||
) : FeatureSettings.Store { | ||
|
||
override fun store(jsonString: String) { | ||
kotlin.runCatching { | ||
appCoroutineScope.launch(dispatchers.io()) { | ||
dataStore.updateInstallSourceFullPackages(jsonString) | ||
} | ||
} | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
...installation/impl/installer/fullpackage/feature/InstallSourceFullPackageListJsonParser.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,57 @@ | ||
/* | ||
* 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.installation.impl.installer.fullpackage.feature | ||
|
||
import com.duckduckgo.di.scopes.AppScope | ||
import com.duckduckgo.installation.impl.installer.fullpackage.InstallSourceFullPackageStore.IncludedPackages | ||
import com.squareup.anvil.annotations.ContributesBinding | ||
import com.squareup.moshi.JsonAdapter | ||
import com.squareup.moshi.Moshi | ||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory | ||
import javax.inject.Inject | ||
|
||
interface InstallSourceFullPackageListJsonParser { | ||
suspend fun parseJson(json: String?): IncludedPackages | ||
} | ||
|
||
@ContributesBinding(AppScope::class) | ||
class InstallSourceFullPackageListJsonParserImpl @Inject constructor() : InstallSourceFullPackageListJsonParser { | ||
|
||
private val jsonAdapter by lazy { buildJsonAdapter() } | ||
|
||
private fun buildJsonAdapter(): JsonAdapter<SettingsJson> { | ||
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() | ||
return moshi.adapter(SettingsJson::class.java) | ||
} | ||
|
||
override suspend fun parseJson(json: String?): IncludedPackages { | ||
if (json == null) return IncludedPackages() | ||
|
||
return kotlin.runCatching { | ||
val parsed = jsonAdapter.fromJson(json) | ||
return parsed?.asIncludedPackages() ?: IncludedPackages() | ||
}.getOrDefault(IncludedPackages()) | ||
} | ||
|
||
private fun SettingsJson.asIncludedPackages(): IncludedPackages { | ||
return IncludedPackages(includedPackages.map { it }) | ||
} | ||
|
||
private data class SettingsJson( | ||
val includedPackages: List<String>, | ||
) | ||
} |
44 changes: 44 additions & 0 deletions
44
.../test/java/com/duckduckgo/installation/impl/installer/fullpackage/IncludedPackagesTest.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,44 @@ | ||
package com.duckduckgo.installation.impl.installer.fullpackage | ||
|
||
import com.duckduckgo.installation.impl.installer.fullpackage.InstallSourceFullPackageStore.IncludedPackages | ||
import org.junit.Assert.assertFalse | ||
import org.junit.Assert.assertTrue | ||
import org.junit.Test | ||
|
||
class IncludedPackagesTest { | ||
|
||
@Test | ||
fun whenEmptyThenDoesNotContainWildcard() { | ||
val list = IncludedPackages(emptyList()) | ||
assertFalse(list.hasWildcard()) | ||
} | ||
|
||
@Test | ||
fun whenHasEntriesButNoWildcardThenDoesNotContainWildcard() { | ||
val list = IncludedPackages( | ||
listOf( | ||
"not a wildcard", | ||
"also not a wildcard", | ||
), | ||
) | ||
assertFalse(list.hasWildcard()) | ||
} | ||
|
||
@Test | ||
fun whenHasMultipleEntriesAndOneIsWildcardEntryThenDoesContainWildcard() { | ||
val list = IncludedPackages( | ||
listOf( | ||
"not a wildcard", | ||
"*", | ||
"also not a wildcard", | ||
), | ||
) | ||
assertTrue(list.hasWildcard()) | ||
} | ||
|
||
@Test | ||
fun whenHasSingleWildcardEntryThenDoesContainWildcard() { | ||
val list = IncludedPackages(listOf("*")) | ||
assertTrue(list.hasWildcard()) | ||
} | ||
} |
Oops, something went wrong.