Skip to content

Commit 699540d

Browse files
Clean up DBs when clear personal data action executed (#1118)
1 parent e657354 commit 699540d

File tree

17 files changed

+274
-26
lines changed

17 files changed

+274
-26
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/WebViewDataManagerTest.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package com.duckduckgo.app.browser
1919
import android.content.Context
2020
import android.webkit.WebStorage
2121
import android.webkit.WebView
22-
import android.webkit.WebViewDatabase
2322
import androidx.test.platform.app.InstrumentationRegistry
2423
import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore
2524
import com.duckduckgo.app.browser.session.WebViewSessionInMemoryStorage
@@ -40,7 +39,6 @@ class WebViewDataManagerTest {
4039
private val mockStorage: WebStorage = mock()
4140
private val context = InstrumentationRegistry.getInstrumentation().targetContext
4241
private val mockFileDeleter: FileDeleter = mock()
43-
private val mockWebViewDatabase: WebViewDatabase = mock()
4442
private val mockWebViewHttpAuthStore: WebViewHttpAuthStore = mock()
4543
private val testee = WebViewDataManager(context, WebViewSessionInMemoryStorage(), mockCookieManager, mockFileDeleter, mockWebViewHttpAuthStore)
4644

@@ -89,6 +87,15 @@ class WebViewDataManagerTest {
8987
}
9088
}
9189

90+
@Test
91+
fun whenDataClearedThenHttpAuthDatabaseCleaned() = runBlocking<Unit> {
92+
withContext(Dispatchers.Main) {
93+
val webView = TestWebView(context)
94+
testee.clearData(webView, mockStorage)
95+
verify(mockWebViewHttpAuthStore).cleanHttpAuthDatabase()
96+
}
97+
}
98+
9299
@Test
93100
fun whenDataClearedThenWebViewCookiesRemoved() = runBlocking<Unit> {
94101
withContext(Dispatchers.Main) {

app/src/androidTest/java/com/duckduckgo/app/browser/httpauth/WebViewHttpAuthStoreTest.kt

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ package com.duckduckgo.app.browser.httpauth
1919
import android.webkit.WebView
2020
import android.webkit.WebViewDatabase
2121
import androidx.test.filters.SdkSuppress
22+
import androidx.test.platform.app.InstrumentationRegistry
2223
import com.duckduckgo.app.CoroutineTestRule
24+
import com.duckduckgo.app.fire.AuthDatabaseLocator
25+
import com.duckduckgo.app.fire.DatabaseCleaner
26+
import com.duckduckgo.app.runBlocking
2327
import com.nhaarman.mockitokotlin2.mock
28+
import com.nhaarman.mockitokotlin2.never
2429
import com.nhaarman.mockitokotlin2.verify
2530
import com.nhaarman.mockitokotlin2.whenever
2631
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -32,13 +37,15 @@ import org.junit.Test
3237
class WebViewHttpAuthStoreTest {
3338

3439
@get:Rule
35-
@Suppress("unused")
3640
val coroutineRule = CoroutineTestRule()
3741

42+
private val context = InstrumentationRegistry.getInstrumentation().targetContext
3843
private val webViewDatabase: WebViewDatabase = mock()
44+
private val mockDatabaseCleaner: DatabaseCleaner = mock()
3945
private val webView: WebView = mock()
46+
private val databaseLocator = AuthDatabaseLocator(context)
4047

41-
private val webViewHttpAuthStore = RealWebViewHttpAuthStore(webViewDatabase)
48+
private val webViewHttpAuthStore = RealWebViewHttpAuthStore(webViewDatabase, mockDatabaseCleaner, databaseLocator, coroutineRule.testDispatcherProvider)
4249

4350
@Test
4451
@SdkSuppress(minSdkVersion = android.os.Build.VERSION_CODES.O)
@@ -89,4 +96,31 @@ class WebViewHttpAuthStoreTest {
8996

9097
assertEquals(WebViewHttpAuthCredentials("name", "pass"), credentials)
9198
}
99+
100+
@Test
101+
fun whenCleanHttpAuthDatabaseThenCleanDatabaseCalled() = coroutineRule.runBlocking {
102+
webViewHttpAuthStore.cleanHttpAuthDatabase()
103+
verify(mockDatabaseCleaner).cleanDatabase(databaseLocator.getDatabasePath())
104+
}
105+
106+
@Test
107+
@SdkSuppress(minSdkVersion = android.os.Build.VERSION_CODES.LOLLIPOP, maxSdkVersion = android.os.Build.VERSION_CODES.O_MR1)
108+
fun whenAppCreatedAndApiBetween21And27ThenJournalModeChangedToDelete() = coroutineRule.runBlocking {
109+
webViewHttpAuthStore.onAppCreated()
110+
verify(mockDatabaseCleaner).changeJournalModeToDelete(databaseLocator.getDatabasePath())
111+
}
112+
113+
@Test
114+
@SdkSuppress(minSdkVersion = android.os.Build.VERSION_CODES.Q)
115+
fun whenAppCreatedAndApiGreaterThan28ThenJournalModeChangedToDelete() = coroutineRule.runBlocking {
116+
webViewHttpAuthStore.onAppCreated()
117+
verify(mockDatabaseCleaner).changeJournalModeToDelete(databaseLocator.getDatabasePath())
118+
}
119+
120+
@Test
121+
@SdkSuppress(minSdkVersion = android.os.Build.VERSION_CODES.P, maxSdkVersion = android.os.Build.VERSION_CODES.P)
122+
fun whenAppCreatedAndApiIs28ThenJournalModeChangedToDeleteNotCalled() = coroutineRule.runBlocking {
123+
webViewHttpAuthStore.onAppCreated()
124+
verify(mockDatabaseCleaner, never()).changeJournalModeToDelete(databaseLocator.getDatabasePath())
125+
}
92126
}

app/src/androidTest/java/com/duckduckgo/app/di/StubDatabaseModule.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ package com.duckduckgo.app.di
1919
import android.content.Context
2020
import android.webkit.WebViewDatabase
2121
import androidx.room.Room
22+
import com.duckduckgo.app.CoroutineTestRule
2223
import com.duckduckgo.app.browser.httpauth.RealWebViewHttpAuthStore
2324
import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore
25+
import com.duckduckgo.app.fire.DatabaseCleanerHelper
26+
import com.duckduckgo.app.fire.AuthDatabaseLocator
2427
import com.duckduckgo.app.global.db.AppDatabase
2528
import com.duckduckgo.di.scopes.AppObjectGraph
2629
import com.squareup.anvil.annotations.ContributesTo
@@ -46,8 +49,8 @@ class StubDatabaseModule {
4649
@Provides
4750
@Singleton
4851
fun provideWebViewHttpAuthStore(
49-
context: Context,
52+
context: Context
5053
): WebViewHttpAuthStore {
51-
return RealWebViewHttpAuthStore(WebViewDatabase.getInstance(context))
54+
return RealWebViewHttpAuthStore(WebViewDatabase.getInstance(context), DatabaseCleanerHelper(), AuthDatabaseLocator(context), CoroutineTestRule().testDispatcherProvider)
5255
}
5356
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2021 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.app.fire
18+
19+
import androidx.test.platform.app.InstrumentationRegistry
20+
import org.junit.Assert.*
21+
import org.junit.Test
22+
23+
class AuthDatabaseLocatorTest {
24+
25+
@Test
26+
fun whenGetDatabasePathOnDeviceThenPathNotEmpty() {
27+
val context = InstrumentationRegistry.getInstrumentation().targetContext
28+
val authDatabaseLocator = AuthDatabaseLocator(context)
29+
30+
val databasePath = authDatabaseLocator.getDatabasePath()
31+
32+
// If this test fails, it means the Auth Database path has changed its location
33+
// If so, add a new database location to knownLocations list
34+
assertTrue(databasePath.isNotEmpty())
35+
}
36+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2021 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.app.fire
18+
19+
import androidx.test.platform.app.InstrumentationRegistry
20+
import kotlinx.coroutines.runBlocking
21+
import org.junit.Assert.*
22+
import org.junit.Before
23+
import org.junit.Test
24+
25+
class DatabaseCleanerHelperTest {
26+
27+
private val context = InstrumentationRegistry.getInstrumentation().targetContext
28+
29+
private lateinit var testee: DatabaseCleanerHelper
30+
private val databaseLocator = AuthDatabaseLocator(context)
31+
32+
@Before
33+
fun before() {
34+
testee = DatabaseCleanerHelper()
35+
}
36+
37+
@Test
38+
fun whenCleanDatabaseThenReturnTrue() = runBlocking {
39+
assertTrue(testee.cleanDatabase(databaseLocator.getDatabasePath()))
40+
}
41+
42+
@Test
43+
fun whenChangeJournalModeToDeleteThenReturnTrue() = runBlocking {
44+
assertTrue(testee.changeJournalModeToDelete(databaseLocator.getDatabasePath()))
45+
}
46+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import javax.inject.Inject
6363
class BrowserActivity : DuckDuckGoActivity(), CoroutineScope by MainScope() {
6464

6565
@Inject
66-
lateinit var clearPersonalDataAction: ClearPersonalDataAction
66+
lateinit var clearPersonalDataAction: ClearDataAction
6767

6868
@Inject
6969
lateinit var dataClearer: DataClearer

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ class WebViewDataManager @Inject constructor(
8080
fileDeleter.deleteContents(File(dataDir, WEBVIEW_DEFAULT_DIRECTORY_NAME), exclusions)
8181
}
8282

83-
private fun clearAuthentication(webView: WebView) {
83+
private suspend fun clearAuthentication(webView: WebView) {
8484
webViewHttpAuthStore.clearHttpAuthUsernamePassword(webView)
85+
webViewHttpAuthStore.cleanHttpAuthDatabase()
8586
}
8687

8788
private suspend fun clearExternalCookies() {
@@ -95,6 +96,7 @@ class WebViewDataManager @Inject constructor(
9596
companion object {
9697
private const val WEBVIEW_DATA_DIRECTORY_NAME = "app_webview"
9798
private const val WEBVIEW_DEFAULT_DIRECTORY_NAME = "app_webview/Default"
99+
private const val DATABASES_DIRECTORY_NAME = "databases"
98100

99101
private val WEBVIEW_FILES_EXCLUDED_FROM_DELETION = listOf(
100102
"Default",

app/src/main/java/com/duckduckgo/app/browser/di/BrowserModule.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import com.duckduckgo.app.tabs.ui.GridViewColumnCalculator
6666
import com.duckduckgo.app.trackerdetection.TrackerDetector
6767
import dagger.Module
6868
import dagger.Provides
69+
import javax.inject.Named
6970
import javax.inject.Singleton
7071

7172
@Module
@@ -192,7 +193,7 @@ class BrowserModule {
192193

193194
@Provides
194195
fun sqlCookieRemover(
195-
webViewDatabaseLocator: WebViewDatabaseLocator,
196+
@Named("webViewDbLocator") webViewDatabaseLocator: DatabaseLocator,
196197
getCookieHostsToPreserve: GetCookieHostsToPreserve,
197198
offlinePixelCountDataStore: OfflinePixelCountDataStore,
198199
exceptionPixel: ExceptionPixel,
@@ -202,7 +203,15 @@ class BrowserModule {
202203
}
203204

204205
@Provides
205-
fun webViewDatabaseLocator(context: Context): WebViewDatabaseLocator = WebViewDatabaseLocator(context)
206+
@Named("webViewDbLocator")
207+
fun webViewDatabaseLocator(context: Context): DatabaseLocator = WebViewDatabaseLocator(context)
208+
209+
@Provides
210+
@Named("authDbLocator")
211+
fun authDatabaseLocator(context: Context): DatabaseLocator = AuthDatabaseLocator(context)
212+
213+
@Provides
214+
fun databaseCleanerHelper(): DatabaseCleaner = DatabaseCleanerHelper()
206215

207216
@Provides
208217
fun getCookieHostsToPreserve(fireproofWebsiteDao: FireproofWebsiteDao): GetCookieHostsToPreserve = GetCookieHostsToPreserve(fireproofWebsiteDao)

app/src/main/java/com/duckduckgo/app/browser/httpauth/WebViewHttpAuthStore.kt

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,49 @@ package com.duckduckgo.app.browser.httpauth
1919
import android.webkit.WebView
2020
import android.webkit.WebViewDatabase
2121
import androidx.annotation.UiThread
22+
import androidx.annotation.WorkerThread
23+
import androidx.lifecycle.Lifecycle
24+
import androidx.lifecycle.LifecycleObserver
25+
import androidx.lifecycle.OnLifecycleEvent
26+
import com.duckduckgo.app.fire.DatabaseCleaner
27+
import com.duckduckgo.app.fire.DatabaseLocator
28+
import com.duckduckgo.app.global.DispatcherProvider
29+
import kotlinx.coroutines.GlobalScope
30+
import kotlinx.coroutines.launch
2231

2332
data class WebViewHttpAuthCredentials(val username: String, val password: String)
2433

2534
// Methods are marked to run in the UiThread because it is the thread of webview
2635
// if necessary the method impls will change thread to access the http auth dao
27-
interface WebViewHttpAuthStore {
36+
interface WebViewHttpAuthStore : LifecycleObserver {
2837
@UiThread
2938
fun setHttpAuthUsernamePassword(webView: WebView, host: String, realm: String, username: String, password: String)
3039
@UiThread
3140
fun getHttpAuthUsernamePassword(webView: WebView, host: String, realm: String): WebViewHttpAuthCredentials?
3241
@UiThread
3342
fun clearHttpAuthUsernamePassword(webView: WebView)
43+
@WorkerThread
44+
suspend fun cleanHttpAuthDatabase()
3445
}
3546

3647
class RealWebViewHttpAuthStore(
37-
private val webViewDatabase: WebViewDatabase
48+
private val webViewDatabase: WebViewDatabase,
49+
private val databaseCleaner: DatabaseCleaner,
50+
private val authDatabaseLocator: DatabaseLocator,
51+
private val dispatcherProvider: DispatcherProvider
3852
) : WebViewHttpAuthStore {
53+
54+
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
55+
fun onAppCreated() {
56+
// API 28 seems to use WAL for the http_auth db and changing the journal mode does not seem
57+
// to work properly
58+
if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.P) return
59+
60+
GlobalScope.launch(dispatcherProvider.io()) {
61+
databaseCleaner.changeJournalModeToDelete(authDatabaseLocator.getDatabasePath())
62+
}
63+
}
64+
3965
override fun setHttpAuthUsernamePassword(webView: WebView, host: String, realm: String, username: String, password: String) {
4066
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
4167
webViewDatabase.setHttpAuthUsernamePassword(host, realm, username, password)
@@ -58,4 +84,8 @@ class RealWebViewHttpAuthStore(
5884
override fun clearHttpAuthUsernamePassword(webView: WebView) {
5985
webViewDatabase.clearHttpAuthUsernamePassword()
6086
}
87+
88+
override suspend fun cleanHttpAuthDatabase() {
89+
databaseCleaner.cleanDatabase(authDatabaseLocator.getDatabasePath())
90+
}
6191
}

app/src/main/java/com/duckduckgo/app/di/DatabaseModule.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ import androidx.room.Room
2222
import com.duckduckgo.app.browser.addtohome.AddToHomeCapabilityDetector
2323
import com.duckduckgo.app.browser.httpauth.RealWebViewHttpAuthStore
2424
import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore
25+
import com.duckduckgo.app.fire.DatabaseCleaner
26+
import com.duckduckgo.app.fire.DatabaseLocator
27+
import com.duckduckgo.app.global.DispatcherProvider
2528
import com.duckduckgo.app.global.db.AppDatabase
2629
import com.duckduckgo.app.global.db.MigrationsProvider
2730
import com.duckduckgo.app.settings.db.SettingsDataStore
2831
import dagger.Module
2932
import dagger.Provides
33+
import javax.inject.Named
3034
import javax.inject.Singleton
3135

3236
@Module(includes = [DaoModule::class])
@@ -36,8 +40,11 @@ class DatabaseModule {
3640
@Singleton
3741
fun provideWebViewHttpAuthStore(
3842
context: Context,
43+
databaseCleaner: DatabaseCleaner,
44+
@Named("authDbLocator") authDatabaseLocator: DatabaseLocator,
45+
dispatcherProvider: DispatcherProvider
3946
): WebViewHttpAuthStore {
40-
return RealWebViewHttpAuthStore(WebViewDatabase.getInstance(context))
47+
return RealWebViewHttpAuthStore(WebViewDatabase.getInstance(context), databaseCleaner, authDatabaseLocator, dispatcherProvider)
4148
}
4249

4350
@Provides

0 commit comments

Comments
 (0)