Skip to content

Commit c64163f

Browse files
committed
Implemented privacy settings
1 parent 063148e commit c64163f

26 files changed

+1279
-19
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.nmc.android.ui
2+
3+
import android.text.Spannable
4+
import android.text.style.ClickableSpan
5+
import android.view.View
6+
import android.widget.TextView
7+
import androidx.test.espresso.UiController
8+
import androidx.test.espresso.ViewAction
9+
import androidx.test.espresso.ViewInteraction
10+
import androidx.test.espresso.assertion.ViewAssertions.matches
11+
import androidx.test.espresso.matcher.BoundedMatcher
12+
import androidx.test.espresso.matcher.ViewMatchers
13+
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
14+
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
15+
import org.hamcrest.Description
16+
import org.hamcrest.Matcher
17+
18+
object ClickableSpanTestHelper {
19+
20+
/**
21+
* method to get clickable span form a text view
22+
* example: val clickableSpan = getClickableSpan("Link text", onView(withId(R.id.text_id)))
23+
*/
24+
fun getClickableSpan(spanText: String, matcher: ViewInteraction?): ClickableSpan? {
25+
val clickableSpans = arrayOf<ClickableSpan?>(null)
26+
27+
// Get the SpannableString from the TextView
28+
matcher?.check(matches(isDisplayed()))
29+
matcher?.perform(object : ViewAction {
30+
override fun getConstraints(): Matcher<View> {
31+
return isAssignableFrom(TextView::class.java)
32+
}
33+
34+
override fun getDescription(): String {
35+
return "get text from TextView"
36+
}
37+
38+
override fun perform(uiController: UiController, view: View) {
39+
val textView = view as TextView
40+
val text = textView.text
41+
if (text is Spannable) {
42+
val spans = text.getSpans(
43+
0, text.length,
44+
ClickableSpan::class.java
45+
)
46+
for (span in spans) {
47+
val start = text.getSpanStart(span)
48+
val end = text.getSpanEnd(span)
49+
val spanString = text.subSequence(start, end).toString()
50+
if (spanString == spanText) {
51+
clickableSpans[0] = span
52+
return
53+
}
54+
}
55+
}
56+
throw java.lang.RuntimeException("ClickableSpan not found")
57+
}
58+
})
59+
return clickableSpans[0]
60+
}
61+
62+
/**
63+
* perform click on the spanned string
64+
* @link getClickableSpan() method to get clickable span
65+
*/
66+
fun performClickSpan(clickableSpan: ClickableSpan?): ViewAction {
67+
return object : ViewAction {
68+
override fun getConstraints(): Matcher<View> {
69+
return ViewMatchers.isAssignableFrom(TextView::class.java)
70+
}
71+
72+
override fun getDescription(): String {
73+
return "clicking on a span"
74+
}
75+
76+
override fun perform(uiController: UiController, view: View) {
77+
val textView = view as TextView
78+
val spannable = textView.text as Spannable
79+
val spans = spannable.getSpans(
80+
0, spannable.length,
81+
ClickableSpan::class.java
82+
)
83+
for (span in spans) {
84+
if (span == clickableSpan) {
85+
span.onClick(textView)
86+
return
87+
}
88+
}
89+
throw RuntimeException("ClickableSpan not found")
90+
}
91+
}
92+
}
93+
94+
fun verifyClickSpan(clickableSpan: ClickableSpan?): Matcher<View?> {
95+
return object : BoundedMatcher<View?, TextView>(TextView::class.java) {
96+
override fun describeTo(description: Description) {
97+
description.appendText("clickable span")
98+
}
99+
100+
override fun matchesSafely(textView: TextView): Boolean {
101+
val spannable = textView.text as Spannable
102+
val spans = spannable.getSpans(
103+
0, spannable.length,
104+
ClickableSpan::class.java
105+
)
106+
for (span in spans) {
107+
if (span == clickableSpan) {
108+
return true
109+
}
110+
}
111+
return false
112+
}
113+
}
114+
}
115+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.nmc.android.ui
2+
3+
import androidx.test.espresso.Espresso.onView
4+
import androidx.test.espresso.Espresso.pressBack
5+
import androidx.test.espresso.action.ViewActions.click
6+
import androidx.test.espresso.assertion.ViewAssertions.matches
7+
import androidx.test.espresso.intent.Intents
8+
import androidx.test.espresso.intent.Intents.intended
9+
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
10+
import androidx.test.espresso.matcher.ViewMatchers.isClickable
11+
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
12+
import androidx.test.espresso.matcher.ViewMatchers.withId
13+
import androidx.test.ext.junit.rules.ActivityScenarioRule
14+
import androidx.test.ext.junit.runners.AndroidJUnit4
15+
import com.nextcloud.client.preferences.AppPreferencesImpl
16+
import com.nmc.android.ui.ClickableSpanTestHelper.getClickableSpan
17+
import com.owncloud.android.AbstractIT
18+
import com.owncloud.android.R
19+
import com.owncloud.android.ui.activity.ExternalSiteWebView
20+
import com.owncloud.android.ui.activity.FileDisplayActivity
21+
import org.junit.Assert.*
22+
import org.junit.Rule
23+
import org.junit.Test
24+
import org.junit.runner.RunWith
25+
26+
@RunWith(AndroidJUnit4::class)
27+
class LoginPrivacySettingsActivityIT : AbstractIT() {
28+
29+
@get:Rule
30+
val activityRule = ActivityScenarioRule(LoginPrivacySettingsActivity::class.java)
31+
32+
@Test
33+
fun verifyNothingHappensOnBackPress() {
34+
pressBack()
35+
shortSleep()
36+
37+
//check any one view to check the activity is not destroyed
38+
onView(withId(R.id.tv_privacy_setting_title)).check(matches(isCompletelyDisplayed()))
39+
}
40+
41+
@Test
42+
fun verifyUIElements() {
43+
onView(withId(R.id.ic_privacy)).check(matches(isCompletelyDisplayed()))
44+
45+
onView(withId(R.id.tv_privacy_setting_title)).check(matches(isCompletelyDisplayed()))
46+
47+
onView(withId(R.id.tv_login_privacy_intro_text)).check(matches(isCompletelyDisplayed()))
48+
49+
onView(withId(R.id.privacy_accept_btn)).check(matches(isCompletelyDisplayed()))
50+
onView(withId(R.id.privacy_accept_btn)).check(matches(isClickable()))
51+
}
52+
53+
@Test
54+
fun verifyAcceptButtonRedirection() {
55+
Intents.init()
56+
onView(withId(R.id.privacy_accept_btn)).perform(click())
57+
58+
//check if the policy action saved correct --> 2 for Accept action
59+
assertEquals(2, AppPreferencesImpl.fromContext(targetContext).privacyPolicyAction)
60+
61+
intended(hasComponent(FileDisplayActivity::class.java.canonicalName))
62+
Intents.release()
63+
}
64+
65+
@Test
66+
fun verifySettingsTextClick() {
67+
Intents.init()
68+
val settingsClickableSpan = getClickableSpan("Settings", onView(withId(R.id.tv_login_privacy_intro_text)))
69+
onView(withId(R.id.tv_login_privacy_intro_text)).perform(
70+
ClickableSpanTestHelper.performClickSpan(
71+
settingsClickableSpan
72+
)
73+
)
74+
intended(hasComponent(PrivacySettingsActivity::class.java.canonicalName))
75+
Intents.release()
76+
}
77+
78+
@Test
79+
fun verifyPrivacyPolicyTextClick() {
80+
Intents.init()
81+
val privacyPolicyClickableSpan =
82+
getClickableSpan("Privacy Policy", onView(withId(R.id.tv_login_privacy_intro_text)))
83+
onView(withId(R.id.tv_login_privacy_intro_text)).perform(
84+
ClickableSpanTestHelper.performClickSpan(
85+
privacyPolicyClickableSpan
86+
)
87+
)
88+
intended(hasComponent(ExternalSiteWebView::class.java.canonicalName))
89+
Intents.release()
90+
}
91+
92+
@Test
93+
fun verifyRejectTextClick() {
94+
Intents.init()
95+
val rejectClickableSpan =
96+
getClickableSpan("reject", onView(withId(R.id.tv_login_privacy_intro_text)))
97+
onView(withId(R.id.tv_login_privacy_intro_text)).perform(
98+
ClickableSpanTestHelper.performClickSpan(
99+
rejectClickableSpan
100+
)
101+
)
102+
103+
//check if the policy action saved correct --> 1 for Reject action
104+
assertEquals(1, AppPreferencesImpl.fromContext(targetContext).privacyPolicyAction)
105+
106+
intended(hasComponent(FileDisplayActivity::class.java.canonicalName))
107+
Intents.release()
108+
}
109+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.nmc.android.ui
2+
3+
import android.content.Intent
4+
import androidx.test.core.app.ActivityScenario
5+
import androidx.test.core.app.launchActivity
6+
import androidx.test.espresso.Espresso.onView
7+
import androidx.test.espresso.action.ViewActions.click
8+
import androidx.test.espresso.assertion.ViewAssertions.matches
9+
import androidx.test.espresso.matcher.ViewMatchers.isChecked
10+
import androidx.test.espresso.matcher.ViewMatchers.isClickable
11+
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
12+
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
13+
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
14+
import androidx.test.espresso.matcher.ViewMatchers.withId
15+
import androidx.test.ext.junit.runners.AndroidJUnit4
16+
import com.owncloud.android.AbstractIT
17+
import com.owncloud.android.R
18+
import org.hamcrest.CoreMatchers.not
19+
import org.junit.After
20+
import org.junit.Before
21+
import org.junit.Test
22+
import org.junit.runner.RunWith
23+
24+
@RunWith(AndroidJUnit4::class)
25+
class PrivacySettingsActivityIT : AbstractIT() {
26+
27+
private fun getIntent(showSettingsButton: Boolean): Intent =
28+
Intent(targetContext, PrivacySettingsActivity::class.java)
29+
.putExtra("show_settings_button", showSettingsButton)
30+
31+
lateinit var activityRule: ActivityScenario<PrivacySettingsActivity>
32+
33+
@Before
34+
fun setUp() {
35+
activityRule = launchActivity(getIntent(false))
36+
}
37+
38+
@Test
39+
fun verifyUIElements() {
40+
onView(withId(R.id.tv_privacy_intro_text)).check(matches(isCompletelyDisplayed()))
41+
42+
onView(withId(R.id.switch_data_collection)).check(matches(isCompletelyDisplayed()))
43+
onView(withId(R.id.switch_data_collection)).check(matches(not(isEnabled())))
44+
onView(withId(R.id.switch_data_collection)).check(matches(isChecked()))
45+
46+
onView(withId(R.id.switch_data_analysis)).check(matches(isCompletelyDisplayed()))
47+
onView(withId(R.id.switch_data_analysis)).check(matches(isEnabled()))
48+
//by-default the analysis switch will be checked as per #AppPreferences.isDataAnalysisEnabled will return true
49+
onView(withId(R.id.switch_data_analysis)).check(matches(isChecked()))
50+
onView(withId(R.id.switch_data_analysis)).check(matches(isClickable()))
51+
52+
onView(withId(R.id.privacy_save_settings_btn)).check(matches(not(isDisplayed())))
53+
}
54+
55+
@Test
56+
fun verifyDataCollectionSwitchToggle() {
57+
//since this button is disabled performing click operation should do nothing
58+
//and switch will be in checked state only
59+
onView(withId(R.id.switch_data_collection)).perform(click())
60+
onView(withId(R.id.switch_data_collection)).check(matches(isChecked()))
61+
62+
onView(withId(R.id.switch_data_collection)).perform(click())
63+
onView(withId(R.id.switch_data_collection)).check(matches(isChecked()))
64+
}
65+
66+
@Test
67+
fun verifyDataAnalysisSwitchToggle() {
68+
onView(withId(R.id.switch_data_analysis)).perform(click())
69+
onView(withId(R.id.switch_data_analysis)).check(matches(not(isChecked())))
70+
71+
onView(withId(R.id.switch_data_analysis)).perform(click())
72+
onView(withId(R.id.switch_data_analysis)).check(matches(isChecked()))
73+
}
74+
75+
@Test
76+
fun verifySaveSettingsButton() {
77+
//button not shown on the basis of extras passed to intent
78+
onView(withId(R.id.privacy_save_settings_btn)).check(matches(not(isDisplayed())))
79+
//close the activity already open
80+
activityRule.close()
81+
82+
//launch activity with extras as true
83+
activityRule = launchActivity(getIntent(true))
84+
//button will be shown if extras is true
85+
onView(withId(R.id.privacy_save_settings_btn)).check(matches(isDisplayed()))
86+
}
87+
88+
@After
89+
fun tearDown() {
90+
activityRule.close()
91+
}
92+
}

app/src/main/AndroidManifest.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,15 @@
631631
android:name=".ui.preview.PreviewBitmapActivity"
632632
android:exported="false"
633633
android:theme="@style/Theme.ownCloud.OverlayGrey" />
634+
<activity
635+
android:name="com.nmc.android.ui.PrivacySettingsActivity"
636+
android:exported="false"
637+
android:windowSoftInputMode="stateAlwaysHidden" />
638+
639+
<activity
640+
android:name="com.nmc.android.ui.LoginPrivacySettingsActivity"
641+
android:exported="false"
642+
android:windowSoftInputMode="stateAlwaysHidden" />
634643
<activity
635644
android:name="com.nextcloud.client.documentscan.DocumentScanActivity"
636645
android:exported="false"

app/src/main/java/com/nextcloud/client/di/ComponentsModule.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
3636
import com.nextcloud.ui.trashbinFileActions.TrashbinFileActionsBottomSheet;
3737
import com.nmc.android.ui.LauncherActivity;
38+
import com.nmc.android.ui.LoginPrivacySettingsActivity;
39+
import com.nmc.android.ui.PrivacySettingsActivity;
3840
import com.owncloud.android.MainApp;
3941
import com.owncloud.android.authentication.AuthenticatorActivity;
4042
import com.owncloud.android.authentication.DeepLinkLoginActivity;
@@ -474,6 +476,12 @@ abstract class ComponentsModule {
474476
@ContributesAndroidInjector
475477
abstract DocumentScanActivity documentScanActivity();
476478

479+
@ContributesAndroidInjector
480+
abstract PrivacySettingsActivity privacySettingsActivity();
481+
482+
@ContributesAndroidInjector
483+
abstract LoginPrivacySettingsActivity loginPrivacySettingsActivity();
484+
477485
@ContributesAndroidInjector
478486
abstract GroupfolderListFragment groupfolderListFragment();
479487

app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,24 @@ default void onDarkThemeModeChanged(DarkMode mode) {
331331

332332
void setCurrentAccountName(String accountName);
333333

334+
/**
335+
* Saves the data analysis from privacy settings
336+
* on disabling it we should disable Adjust SDK tracking
337+
*
338+
* @param enableDataAnalysis to enable/disable data analysis
339+
*/
340+
void setDataAnalysis(boolean enableDataAnalysis);
341+
boolean isDataAnalysisEnabled();
342+
343+
/**
344+
* Saves the privacy policy action taken by user
345+
* this will maintain the state of current privacy policy action taken
346+
* @see com.nmc.android.ui.LoginPrivacySettingsActivity for actions
347+
* @param userAction taken by user
348+
*/
349+
void setPrivacyPolicyAction(int userAction);
350+
int getPrivacyPolicyAction();
351+
334352
/**
335353
* Gets status of migration to user id, default false
336354
*

0 commit comments

Comments
 (0)