Skip to content

Commit

Permalink
Backup service (#3952)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/0/1206040339100181/f

### Description
Implemented backup key/value service

### Steps to test this PR

_ATB mapped to BackUp prefs_
- Make sure you have a previous version of the app installed
- Put a log on _Line 35_ of `ReinstallAtbListener` to check preference
mapping
- Install from branch
- [x] Check log appears in Logcat

_Backup service_
- Install from branch
- Run `./test_cloud_backup.sh com.duckduckgo.mobile.android.debug` in
the terminal
- [x] Check backup is success

_Pixel sent when Backup service enabled_
- Install from this branch
- Run `./test_cloud_backup.sh com.duckduckgo.mobile.android.debug` in
the terminal
- Wait until backup is success
- Go to Logcat
- [x] Check `m_backup_service_enabled` pixel is sent


### No UI changes
  • Loading branch information
nalcalag authored Dec 4, 2023
1 parent 5f1fceb commit cd9a695
Show file tree
Hide file tree
Showing 21 changed files with 590 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class ContributesSubComponentCodeGenerator : CodeGenerator {
quickSettingsScopeFqName.asClassName(module) -> appScopeFqName
fragmentScopeFqName.asClassName(module) -> activityScopeFqName
viewScopeFqName.asClassName(module) -> activityScopeFqName
backupAgentScopeFqName.asClassName(module) -> appScopeFqName
else -> throw AnvilCompilationException("${this.asClassName(module)} scope is not currently supported")
}
}
Expand Down Expand Up @@ -232,5 +233,6 @@ class ContributesSubComponentCodeGenerator : CodeGenerator {
private val vpnScopeFqName = FqName("com.duckduckgo.di.scopes.VpnScope")
private val quickSettingsScopeFqName = FqName("com.duckduckgo.di.scopes.QuickSettingsScope")
private val viewScopeFqName = FqName("com.duckduckgo.di.scopes.ViewScope")
private val backupAgentScopeFqName = FqName("com.duckduckgo.di.scopes.BackupAgentScope")
}
}
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ dependencies {
implementation project(':broken-site-impl')
implementation project(':broken-site-store')

implementation project(':backup-agent-api')
implementation project(':backup-agent-impl')

// Deprecated. TODO: Stop using this artifact.
implementation "androidx.legacy:legacy-support-v4:_"
debugImplementation Square.leakCanary.android
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

<application
android:name="com.duckduckgo.app.global.DuckDuckGoApplication"
android:allowBackup="false"
android:backupAgent="com.duckduckgo.app.backup.agent.impl.DuckDuckGoBackupAgent"
android:icon="${appIcon}"
android:label="@string/appName"
android:networkSecurityConfig="@xml/network_security_config"
Expand Down
1 change: 1 addition & 0 deletions backup-agent/backup-agent-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
37 changes: 37 additions & 0 deletions backup-agent/backup-agent-api/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2023 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.
*/

plugins {
id 'java-library'
id 'kotlin'
}

apply from: "$rootProject.projectDir/code-formatting.gradle"

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlin {
jvmToolchain(17)
}

dependencies {
implementation Kotlin.stdlib.jdk7

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 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.backup.agent.api

/** Public interface for backup agent feature*/
interface BackupAgentManager {

/**
* Returns true if this is a returning user, false otherwise
*
* @param variantKey is the current user variant
*/
fun isReinstallUser(variantKey: String?): Boolean
}
1 change: 1 addition & 0 deletions backup-agent/backup-agent-impl/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
74 changes: 74 additions & 0 deletions backup-agent/backup-agent-impl/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2021 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.
*/

plugins {
id 'com.android.library'
id 'kotlin-android'
id 'com.squareup.anvil'
}

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

dependencies {
anvil project(path: ':anvil-compiler')
implementation project(path: ':anvil-annotations')

implementation project(path: ':di')
implementation project(path: ':statistics')
implementation project(path: ':backup-agent-api')

implementation Kotlin.stdlib.jdk7
implementation AndroidX.appCompat
implementation JakeWharton.timber

implementation KotlinX.coroutines.core

implementation Google.dagger

testImplementation (KotlinX.coroutines.test) {
// https://github.com/Kotlin/kotlinx.coroutines/issues/2023
// conflicts with mockito due to direct inclusion of byte buddy
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
}

// Testing dependencies
testImplementation project(path: ':common-test')
testImplementation AndroidX.work.testing
testImplementation AndroidX.room.testing
testImplementation "org.mockito.kotlin:mockito-kotlin:_"
testImplementation Testing.junit4
testImplementation AndroidX.archCore.testing
testImplementation AndroidX.core
testImplementation AndroidX.test.ext.junit
testImplementation "androidx.test:runner:_"
testImplementation Testing.robolectric
testImplementation CashApp.turbine

testImplementation project(path: ':common-test')
}

android {
anvil {
generateDaggerFactories = true // default is false
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
namespace 'com.duckduckgo.backup.agent.imp'
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 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.backup.agent.impl

import com.duckduckgo.backup.agent.api.BackupAgentManager
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesBinding
import javax.inject.Inject

@ContributesBinding(AppScope::class)
class BackupAgentManagerImpl @Inject constructor() : BackupAgentManager {

override fun isReinstallUser(variantKey: String?): Boolean {
return variantKey == REINSTALL_VARIANT
}

companion object {
const val REINSTALL_VARIANT = "ru"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 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.backup.agent.impl

import android.app.backup.BackupAgentHelper
import android.app.backup.BackupDataOutput
import android.app.backup.SharedPreferencesBackupHelper
import android.os.ParcelFileDescriptor
import com.duckduckgo.anvil.annotations.InjectWith
import com.duckduckgo.app.backup.agent.impl.pixel.BackupAgentPixelName.BACKUP_SERVICE_ENABLED
import com.duckduckgo.app.backup.agent.impl.store.BackupPixelDataStore
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.di.scopes.BackupAgentScope
import dagger.android.AndroidInjection
import javax.inject.Inject

@InjectWith(BackupAgentScope::class)
class DuckDuckGoBackupAgent : BackupAgentHelper() {

@Inject lateinit var pixel: Pixel

@Inject lateinit var backupPixelDataStore: BackupPixelDataStore

override fun onCreate() {
super.onCreate()
AndroidInjection.inject(this)
SharedPreferencesBackupHelper(this, FILENAME).also {
addHelper(FILENAME_BACKUP, it)
}
}

override fun onBackup(
oldState: ParcelFileDescriptor?,
data: BackupDataOutput?,
newState: ParcelFileDescriptor?,
) {
super.onBackup(oldState, data, newState)
if (!backupPixelDataStore.backupEnabledPixelSent) {
pixel.fire(BACKUP_SERVICE_ENABLED)
backupPixelDataStore.backupEnabledPixelSent = true
}
}

companion object {
private const val FILENAME = "com.duckduckgo.app.statistics.backup"
private const val FILENAME_BACKUP = "com.duckduckgo.app.backup.backup"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2023 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.backup.agent.impl

import com.duckduckgo.app.backup.agent.impl.BackupAgentManagerImpl.Companion.REINSTALL_VARIANT
import com.duckduckgo.app.backup.agent.impl.store.BackupDataStore
import com.duckduckgo.app.statistics.AtbInitializerListener
import com.duckduckgo.app.statistics.store.StatisticsDataStore
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesMultibinding
import dagger.SingleInstanceIn
import javax.inject.Inject
import timber.log.Timber

@SingleInstanceIn(AppScope::class)
@ContributesMultibinding(AppScope::class)
class ReinstallAtbListener @Inject constructor(
private val statisticsDataStore: StatisticsDataStore,
private val backupDataStore: BackupDataStore,
) : AtbInitializerListener {
override suspend fun beforeAtbInit() {
if (statisticsDataStore.hasInstallationStatistics && backupDataStore.atb != statisticsDataStore.atb) {
backupDataStore.atb = statisticsDataStore.atb
}
if (!statisticsDataStore.hasInstallationStatistics && backupDataStore.atb != null) {
backupDataStore.atb = null
statisticsDataStore.variant = REINSTALL_VARIANT
Timber.d("Variant update for returning user")
}
}

override fun beforeAtbInitTimeoutMillis(): Long = MAX_REINSTALL_WAIT_TIME_MS

companion object {
private const val MAX_REINSTALL_WAIT_TIME_MS = 1_500L
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2023 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.backup.agent.impl.pixel

import com.duckduckgo.app.statistics.pixels.Pixel

enum class BackupAgentPixelName(override val pixelName: String) : Pixel.PixelName {
BACKUP_SERVICE_ENABLED("m_backup_service_enabled"),
}
Loading

0 comments on commit cd9a695

Please sign in to comment.