Skip to content

Commit f42c4b4

Browse files
authored
Add privacy grade to omnibar
1 parent 4a40d4e commit f42c4b4

File tree

12 files changed

+227
-31
lines changed

12 files changed

+227
-31
lines changed

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

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import android.arch.core.executor.testing.InstantTaskExecutorRule
2020
import android.arch.lifecycle.Observer
2121
import android.net.Uri
2222
import com.duckduckgo.app.browser.BrowserViewModel.NavigationCommand
23-
import com.duckduckgo.app.browser.BrowserViewModel.ViewState
2423
import com.duckduckgo.app.browser.omnibar.OmnibarEntryConverter
24+
import com.duckduckgo.app.privacymonitor.model.PrivacyGrade
2525
import com.duckduckgo.app.privacymonitor.store.PrivacyMonitorRepository
2626
import com.duckduckgo.app.privacymonitor.store.TermsOfServiceStore
2727
import com.duckduckgo.app.trackerdetection.model.TrackerNetworks
28+
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
2829
import com.nhaarman.mockito_kotlin.mock
2930
import org.junit.After
3031
import org.junit.Assert.*
@@ -41,7 +42,6 @@ class BrowserViewModelTest {
4142
@Suppress("unused")
4243
var instantTaskExecutorRule = InstantTaskExecutorRule()
4344

44-
private lateinit var viewStateObserver: Observer<ViewState>
4545
private lateinit var queryObserver: Observer<String>
4646
private lateinit var navigationObserver: Observer<NavigationCommand>
4747
private lateinit var termsOfServiceStore: TermsOfServiceStore
@@ -55,20 +55,17 @@ class BrowserViewModelTest {
5555

5656
@Before
5757
fun before() {
58-
viewStateObserver = mock()
5958
queryObserver = mock()
6059
navigationObserver = mock()
6160
termsOfServiceStore = mock()
6261
testee = BrowserViewModel(testOmnibarConverter, DuckDuckGoUrlDetector(), termsOfServiceStore, TrackerNetworks(), PrivacyMonitorRepository())
6362
testee.query.observeForever(queryObserver)
64-
testee.viewState.observeForever(viewStateObserver)
6563
testee.navigation.observeForever(navigationObserver)
6664
}
6765

6866
@After
6967
fun after() {
7068
testee.query.removeObserver(queryObserver)
71-
testee.viewState.removeObserver(viewStateObserver)
7269
testee.navigation.removeObserver(navigationObserver)
7370
}
7471

@@ -159,4 +156,34 @@ class BrowserViewModelTest {
159156
testee.urlChanged("")
160157
assertFalse(testee.userDismissedKeyboard())
161158
}
159+
160+
@Test
161+
fun whenLoadingStartedThenPrivacyGradeIsCleared() {
162+
testee.loadingStarted()
163+
assertNull(testee.viewState.value!!.privacyGrade)
164+
}
165+
166+
@Test
167+
fun whenUrlChangedThenPrivacyGradeIsReset() {
168+
testee.urlChanged("https://example.com")
169+
assertEquals(PrivacyGrade.B, testee.viewState.value!!.privacyGrade)
170+
}
171+
172+
@Test
173+
fun whenTrackerDetectedThenPrivacyGradeIsUpdated() {
174+
testee.urlChanged("https://example.com")
175+
testee.trackerDetected(TrackingEvent("", "", null, false))
176+
assertEquals(PrivacyGrade.C, testee.viewState.value!!.privacyGrade)
177+
}
178+
179+
@Test
180+
fun whenInitialisedThenPrivacyGradeIsNotShown() {
181+
assertFalse(testee.viewState.value!!.showPrivacyGrade)
182+
}
183+
184+
@Test
185+
fun whenUrlUpdatedThenPrivacyGradeIsShown() {
186+
testee.urlChanged((""))
187+
assertTrue(testee.viewState.value!!.showPrivacyGrade)
188+
}
162189
}

app/src/androidTest/java/com/duckduckgo/app/privacymonitor/PrivacyMonitorGradeExtensionTest.kt

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.duckduckgo.app.privacymonitor
1919
import com.duckduckgo.app.privacymonitor.HttpsStatus.*
2020
import com.duckduckgo.app.privacymonitor.model.TermsOfService
2121
import com.duckduckgo.app.privacymonitor.ui.improvedScore
22+
import com.duckduckgo.app.privacymonitor.ui.potentialScore
2223
import com.duckduckgo.app.privacymonitor.ui.score
2324
import com.duckduckgo.app.trackerdetection.model.TrackerNetwork
2425
import com.nhaarman.mockito_kotlin.mock
@@ -134,9 +135,8 @@ class PrivacyMonitorGradeExtensionTest {
134135
assertEquals(defaultScore + 1, privacyMonitor.score)
135136
}
136137

137-
138138
@Test
139-
fun whenImprovedScoreThenTrackerMetricsIgnored() {
139+
fun whenPotentialScoreThenTrackerMetricsIgnored() {
140140
val privacyMonitor = monitor(
141141
TrackerNetwork("", "", 5, true),
142142
TermsOfService(classification = "D"),
@@ -145,23 +145,50 @@ class PrivacyMonitorGradeExtensionTest {
145145
2,
146146
true)
147147
assertEquals(defaultScore + 6, privacyMonitor.score)
148-
assertEquals(defaultScore + 3, privacyMonitor.improvedScore)
148+
assertEquals(defaultScore + 3, privacyMonitor.potentialScore)
149+
}
150+
151+
@Test
152+
fun whenAllTrackersBlockedThenImporvedScoreIsEqualToPotentialScore() {
153+
val privacyMonitor = monitor(
154+
TrackerNetwork("", "", 5, true),
155+
TermsOfService(classification = "D"),
156+
NONE,
157+
5,
158+
2,
159+
true,
160+
allTrackerBlocked = true)
161+
assertEquals(privacyMonitor.potentialScore, privacyMonitor.improvedScore)
162+
}
163+
164+
@Test
165+
fun whenNotAllTrackersBlockedThenImprovedScoreIsEqualToScore() {
166+
val privacyMonitor = monitor(
167+
TrackerNetwork("", "", 5, true),
168+
TermsOfService(classification = "D"),
169+
NONE,
170+
5,
171+
2,
172+
true,
173+
allTrackerBlocked = false)
174+
assertEquals(privacyMonitor.score, privacyMonitor.improvedScore)
149175
}
150176

151177
private fun monitor(memberNetwork: TrackerNetwork? = null,
152178
terms: TermsOfService = TermsOfService(),
153179
https: HttpsStatus = SECURE,
154180
trackerCount: Int = 0,
155181
majorTrackerCount: Int = 0,
156-
hasObscureTracker: Boolean = false): PrivacyMonitor {
157-
182+
hasObscureTracker: Boolean = false,
183+
allTrackerBlocked: Boolean = true): PrivacyMonitor {
158184
val monitor: PrivacyMonitor = mock()
159185
whenever(monitor.memberNetwork).thenReturn(memberNetwork)
160186
whenever(monitor.termsOfService).thenReturn(terms)
161187
whenever(monitor.trackerCount).thenReturn(trackerCount)
162188
whenever(monitor.majorNetworkCount).thenReturn(majorTrackerCount)
163189
whenever(monitor.hasObscureTracker).thenReturn(hasObscureTracker)
164190
whenever(monitor.https).thenReturn(https)
191+
whenever(monitor.allTrackersBlocked).thenReturn(allTrackerBlocked)
165192
return monitor
166193
}
167194
}

app/src/androidTest/java/com/duckduckgo/app/privacymonitor/ui/PrivacyDashboardViewModelTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ class PrivacyDashboardViewModelTest {
130130
whenever(settingStore.privacyOn).thenReturn(false)
131131
val monitor = monitor()
132132
whenever(monitor.allTrackersBlocked).thenReturn(false)
133-
whenever(monitor.majorNetworkCount).thenReturn(2)
134133
testee.onPrivacyMonitorChanged(monitor)
135134
assertEquals(getStringResource(R.string.privacyProtectionDisabled), testee.viewState.value?.heading)
136135
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import com.duckduckgo.app.browser.omnibar.OnBackKeyListener
3535
import com.duckduckgo.app.global.DuckDuckGoActivity
3636
import com.duckduckgo.app.global.ViewModelFactory
3737
import com.duckduckgo.app.global.view.*
38+
import com.duckduckgo.app.privacymonitor.model.PrivacyGrade
39+
import com.duckduckgo.app.privacymonitor.model.PrivacyGrade.Companion.Grade
3840
import com.duckduckgo.app.privacymonitor.ui.PrivacyDashboardActivity
3941
import com.duckduckgo.app.privacymonitor.ui.PrivacyDashboardActivity.Companion.REQUEST_DASHBOARD
4042
import com.duckduckgo.app.privacymonitor.ui.PrivacyDashboardActivity.Companion.RESULT_RELOAD
@@ -110,6 +112,8 @@ class BrowserActivity : DuckDuckGoActivity() {
110112
true -> showClearButton()
111113
false -> hideClearButton()
112114
}
115+
116+
updatePrivacyGrade(viewState.privacyGrade, viewState.showPrivacyGrade)
113117
}
114118

115119
private fun showClearButton() {
@@ -126,6 +130,19 @@ class BrowserActivity : DuckDuckGoActivity() {
126130
}
127131
}
128132

133+
private fun updatePrivacyGrade(@Grade privacyGrade: Long?, show: Boolean) {
134+
val resource = when (privacyGrade) {
135+
PrivacyGrade.A -> R.drawable.privacygrade_icon_a
136+
PrivacyGrade.B -> R.drawable.privacygrade_icon_b
137+
PrivacyGrade.C -> R.drawable.privacygrade_icon_c
138+
PrivacyGrade.D -> R.drawable.privacygrade_icon_d
139+
else -> R.drawable.privacygrade_icon_unknown
140+
}
141+
val menuItem = toolbar.menu.findItem(R.id.privacy_dashboard)
142+
menuItem?.icon = getDrawable(resource)
143+
menuItem?.isVisible = show
144+
}
145+
129146
private fun shouldUpdateUrl(viewState: BrowserViewModel.ViewState, url: String?) =
130147
viewState.url != null && !viewState.isEditing && urlInput.isDifferent(url)
131148

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ import android.arch.lifecycle.ViewModel
2121
import com.duckduckgo.app.browser.omnibar.OmnibarEntryConverter
2222
import com.duckduckgo.app.global.SingleLiveEvent
2323
import com.duckduckgo.app.privacymonitor.SiteMonitor
24+
import com.duckduckgo.app.privacymonitor.model.PrivacyGrade.Companion.Grade
2425
import com.duckduckgo.app.privacymonitor.model.TermsOfService
2526
import com.duckduckgo.app.privacymonitor.store.PrivacyMonitorRepository
2627
import com.duckduckgo.app.privacymonitor.store.TermsOfServiceStore
28+
import com.duckduckgo.app.privacymonitor.ui.improvedGrade
2729
import com.duckduckgo.app.trackerdetection.model.TrackerNetworks
2830
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
2931
import timber.log.Timber
@@ -42,7 +44,9 @@ class BrowserViewModel(
4244
val url: String? = null,
4345
val isEditing: Boolean = false,
4446
val browserShowing: Boolean = false,
45-
val showClearButton: Boolean = false
47+
val showClearButton: Boolean = false,
48+
@Grade val privacyGrade: Long? = null,
49+
val showPrivacyGrade: Boolean = false
4650
)
4751

4852
/* Observable data for Activity to subscribe to */
@@ -94,7 +98,7 @@ class BrowserViewModel(
9498
Timber.v("Loading started")
9599
viewState.value = currentViewState().copy(isLoading = true)
96100
siteMonitor = null
97-
postSiteMonitor()
101+
onSiteMonitorChanged()
98102
}
99103

100104
override fun loadingFinished() {
@@ -104,7 +108,7 @@ class BrowserViewModel(
104108

105109
override fun urlChanged(url: String?) {
106110
Timber.v("Url changed: $url")
107-
var newViewState = currentViewState().copy(url = url, browserShowing = true)
111+
var newViewState = currentViewState().copy(url = url, browserShowing = true, showPrivacyGrade = true)
108112

109113
if (duckDuckGoUrlDetector.isDuckDuckGoUrl(url)) {
110114
newViewState = newViewState.copy(url = lastQuery)
@@ -113,21 +117,22 @@ class BrowserViewModel(
113117
if (url != null) {
114118
val terms = termsOfServiceStore.retrieveTerms(url) ?: TermsOfService()
115119
siteMonitor = SiteMonitor(url, terms, trackerNetworks)
116-
postSiteMonitor()
120+
onSiteMonitorChanged()
117121
}
118122
}
119123

120124
override fun trackerDetected(event: TrackingEvent) {
121125
siteMonitor?.trackerDetected(event)
122-
postSiteMonitor()
126+
onSiteMonitorChanged()
123127
}
124128

125129
override fun pageHasHttpResources() {
126130
siteMonitor?.hasHttpResources = true
127-
postSiteMonitor()
131+
onSiteMonitorChanged()
128132
}
129133

130-
private fun postSiteMonitor() {
134+
private fun onSiteMonitorChanged() {
135+
viewState.postValue(currentViewState().copy(privacyGrade = siteMonitor?.improvedGrade))
131136
privacyMonitorRepository.privacyMonitor.postValue(siteMonitor)
132137
}
133138

app/src/main/java/com/duckduckgo/app/privacymonitor/ui/PrivacyDashboardViewModel.kt

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class PrivacyDashboardViewModel(private val context: Context,
8989
private fun updatePrivacyMonitor(monitor: PrivacyMonitor) {
9090
this.monitor = monitor
9191
viewState.value = viewState.value?.copy(
92-
privacyBanner = privacyBanner(),
92+
privacyBanner = privacyBanner(monitor.improvedGrade),
9393
domain = monitor.uri?.host ?: "",
9494
heading = headingText(),
9595
httpsIcon = httpsIcon(monitor.https),
@@ -108,15 +108,15 @@ class PrivacyDashboardViewModel(private val context: Context,
108108
settingsStore.privacyOn = enabled
109109
viewState.value = viewState.value?.copy(
110110
heading = headingText(),
111-
privacyBanner = privacyBanner(),
111+
privacyBanner = privacyBanner(monitor?.improvedGrade),
112112
toggleEnabled = enabled
113113
)
114114
}
115115
}
116116

117117
private fun headingText(): String {
118118
val monitor = monitor
119-
if (monitor != null && monitor.allTrackersBlocked) {
119+
if (monitor != null) {
120120
val before = monitor.grade
121121
val after = monitor.improvedGrade
122122
if (before != after) {
@@ -127,15 +127,6 @@ class PrivacyDashboardViewModel(private val context: Context,
127127
return context.getString(resource)
128128
}
129129

130-
@DrawableRes
131-
private fun privacyBanner(): Int {
132-
val monitor = monitor ?: return R.drawable.privacygrade_banner_unknown
133-
if (monitor.allTrackersBlocked) {
134-
return privacyBanner(monitor.improvedGrade)
135-
}
136-
return privacyBanner(monitor.grade)
137-
}
138-
139130
private fun privacyBanner(grade: Long?): Int {
140131
if (settingsStore.privacyOn) {
141132
return privacyBannerOn(grade)

app/src/main/java/com/duckduckgo/app/privacymonitor/ui/PrivacyMonitorGradeExtension.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ val PrivacyMonitor.score: Int
3535
return score
3636
}
3737

38-
val PrivacyMonitor.improvedScore: Int
38+
val PrivacyMonitor.potentialScore: Int
3939
get() = baseScore
4040

4141

@@ -50,14 +50,27 @@ private val PrivacyMonitor.baseScore: Int
5050
return score
5151
}
5252

53+
val PrivacyMonitor.improvedScore: Int
54+
get() {
55+
if (allTrackersBlocked) {
56+
return potentialScore
57+
}
58+
return score
59+
}
60+
5361
@PrivacyGrade.Companion.Grade
5462
val PrivacyMonitor.grade: Long
5563
get() = calculateGrade(score)
5664

65+
@PrivacyGrade.Companion.Grade
66+
val PrivacyMonitor.potentialGrade: Long
67+
get() = calculateGrade(potentialScore)
68+
5769
@PrivacyGrade.Companion.Grade
5870
val PrivacyMonitor.improvedGrade: Long
5971
get() = calculateGrade(improvedScore)
6072

73+
6174
@PrivacyGrade.Companion.Grade
6275
private fun calculateGrade(score: Int): Long {
6376
return when {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!--
2+
~ Copyright (c) 2017 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+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
18+
android:width="22dp"
19+
android:height="22dp"
20+
android:viewportWidth="22.0"
21+
android:viewportHeight="22.0">
22+
<path
23+
android:pathData="M9.74,12.305h2.52l-1.25,-3.74z"
24+
android:fillType="nonZero"
25+
android:fillColor="#A7A8AB"/>
26+
<path
27+
android:pathData="M11,0C4.925,0 0,4.925 0,11s4.925,11 11,11 11,-4.925 11,-11A11,11 0,0 0,11 0zM13.45,15.965l-0.5,-1.43L9.045,14.535l-0.5,1.43L5.65,15.965l3.735,-9.93h3.23l3.735,9.93h-2.9z"
28+
android:fillType="nonZero"
29+
android:fillColor="#A7A8AB"/>
30+
</vector>

0 commit comments

Comments
 (0)