Skip to content

Commit

Permalink
Implemented privacy settings
Browse files Browse the repository at this point in the history
  • Loading branch information
surinder-tsys committed Feb 8, 2024
1 parent 9e6ac2c commit 8f7c5be
Show file tree
Hide file tree
Showing 26 changed files with 1,274 additions and 15 deletions.
115 changes: 115 additions & 0 deletions app/src/androidTest/java/com/nmc/android/ui/ClickableSpanTestHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.nmc.android.ui

import android.text.Spannable
import android.text.style.ClickableSpan
import android.view.View
import android.widget.TextView
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.BoundedMatcher
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import org.hamcrest.Description
import org.hamcrest.Matcher

object ClickableSpanTestHelper {

/**
* method to get clickable span form a text view
* example: val clickableSpan = getClickableSpan("Link text", onView(withId(R.id.text_id)))
*/
fun getClickableSpan(spanText: String, matcher: ViewInteraction?): ClickableSpan? {
val clickableSpans = arrayOf<ClickableSpan?>(null)

// Get the SpannableString from the TextView
matcher?.check(matches(isDisplayed()))
matcher?.perform(object : ViewAction {
override fun getConstraints(): Matcher<View> {
return isAssignableFrom(TextView::class.java)
}

override fun getDescription(): String {
return "get text from TextView"
}

override fun perform(uiController: UiController, view: View) {
val textView = view as TextView
val text = textView.text
if (text is Spannable) {
val spans = text.getSpans(
0, text.length,
ClickableSpan::class.java
)
for (span in spans) {
val start = text.getSpanStart(span)
val end = text.getSpanEnd(span)
val spanString = text.subSequence(start, end).toString()
if (spanString == spanText) {
clickableSpans[0] = span
return
}
}
}
throw java.lang.RuntimeException("ClickableSpan not found")
}
})
return clickableSpans[0]
}

/**
* perform click on the spanned string
* @link getClickableSpan() method to get clickable span
*/
fun performClickSpan(clickableSpan: ClickableSpan?): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> {
return ViewMatchers.isAssignableFrom(TextView::class.java)
}

override fun getDescription(): String {
return "clicking on a span"
}

override fun perform(uiController: UiController, view: View) {
val textView = view as TextView
val spannable = textView.text as Spannable
val spans = spannable.getSpans(
0, spannable.length,
ClickableSpan::class.java
)
for (span in spans) {
if (span == clickableSpan) {
span.onClick(textView)
return
}
}
throw RuntimeException("ClickableSpan not found")
}
}
}

fun verifyClickSpan(clickableSpan: ClickableSpan?): Matcher<View?> {
return object : BoundedMatcher<View?, TextView>(TextView::class.java) {
override fun describeTo(description: Description) {
description.appendText("clickable span")
}

override fun matchesSafely(textView: TextView): Boolean {
val spannable = textView.text as Spannable
val spans = spannable.getSpans(
0, spannable.length,
ClickableSpan::class.java
)
for (span in spans) {
if (span == clickableSpan) {
return true
}
}
return false
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.nmc.android.ui

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.matcher.ViewMatchers.isClickable
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nextcloud.client.preferences.AppPreferencesImpl
import com.nmc.android.ui.ClickableSpanTestHelper.getClickableSpan
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.ui.activity.ExternalSiteWebView
import com.owncloud.android.ui.activity.FileDisplayActivity
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class LoginPrivacySettingsActivityIT : AbstractIT() {

@get:Rule
val activityRule = ActivityScenarioRule(LoginPrivacySettingsActivity::class.java)

@Test
fun verifyNothingHappensOnBackPress() {
pressBack()
shortSleep()

//check any one view to check the activity is not destroyed
onView(withId(R.id.tv_privacy_setting_title)).check(matches(isCompletelyDisplayed()))
}

@Test
fun verifyUIElements() {
onView(withId(R.id.ic_privacy)).check(matches(isCompletelyDisplayed()))

onView(withId(R.id.tv_privacy_setting_title)).check(matches(isCompletelyDisplayed()))

onView(withId(R.id.tv_login_privacy_intro_text)).check(matches(isCompletelyDisplayed()))

onView(withId(R.id.privacy_accept_btn)).check(matches(isCompletelyDisplayed()))
onView(withId(R.id.privacy_accept_btn)).check(matches(isClickable()))
}

@Test
fun verifyAcceptButtonRedirection() {
Intents.init()
onView(withId(R.id.privacy_accept_btn)).perform(click())

//check if the policy action saved correct --> 2 for Accept action
assertEquals(2, AppPreferencesImpl.fromContext(targetContext).privacyPolicyAction)

intended(hasComponent(FileDisplayActivity::class.java.canonicalName))
Intents.release()
}

@Test
fun verifySettingsTextClick() {
Intents.init()
val settingsClickableSpan = getClickableSpan("Settings", onView(withId(R.id.tv_login_privacy_intro_text)))
onView(withId(R.id.tv_login_privacy_intro_text)).perform(
ClickableSpanTestHelper.performClickSpan(
settingsClickableSpan
)
)
intended(hasComponent(PrivacySettingsActivity::class.java.canonicalName))
Intents.release()
}

@Test
fun verifyPrivacyPolicyTextClick() {
Intents.init()
val privacyPolicyClickableSpan =
getClickableSpan("Privacy Policy", onView(withId(R.id.tv_login_privacy_intro_text)))
onView(withId(R.id.tv_login_privacy_intro_text)).perform(
ClickableSpanTestHelper.performClickSpan(
privacyPolicyClickableSpan
)
)
intended(hasComponent(ExternalSiteWebView::class.java.canonicalName))
Intents.release()
}

@Test
fun verifyRejectTextClick() {
Intents.init()
val rejectClickableSpan =
getClickableSpan("reject", onView(withId(R.id.tv_login_privacy_intro_text)))
onView(withId(R.id.tv_login_privacy_intro_text)).perform(
ClickableSpanTestHelper.performClickSpan(
rejectClickableSpan
)
)

//check if the policy action saved correct --> 1 for Reject action
assertEquals(1, AppPreferencesImpl.fromContext(targetContext).privacyPolicyAction)

intended(hasComponent(FileDisplayActivity::class.java.canonicalName))
Intents.release()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.nmc.android.ui

import android.content.Intent
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isClickable
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import org.hamcrest.CoreMatchers.not
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class PrivacySettingsActivityIT : AbstractIT() {

private fun getIntent(showSettingsButton: Boolean): Intent =
Intent(targetContext, PrivacySettingsActivity::class.java)
.putExtra("show_settings_button", showSettingsButton)

lateinit var activityRule: ActivityScenario<PrivacySettingsActivity>

@Before
fun setUp() {
activityRule = launchActivity(getIntent(false))
}

@Test
fun verifyUIElements() {
onView(withId(R.id.tv_privacy_intro_text)).check(matches(isCompletelyDisplayed()))

onView(withId(R.id.switch_data_collection)).check(matches(isCompletelyDisplayed()))
onView(withId(R.id.switch_data_collection)).check(matches(not(isEnabled())))
onView(withId(R.id.switch_data_collection)).check(matches(isChecked()))

onView(withId(R.id.switch_data_analysis)).check(matches(isCompletelyDisplayed()))
onView(withId(R.id.switch_data_analysis)).check(matches(isEnabled()))
//by-default the analysis switch will be checked as per #AppPreferences.isDataAnalysisEnabled will return true
onView(withId(R.id.switch_data_analysis)).check(matches(isChecked()))
onView(withId(R.id.switch_data_analysis)).check(matches(isClickable()))

onView(withId(R.id.privacy_save_settings_btn)).check(matches(not(isDisplayed())))
}

@Test
fun verifyDataCollectionSwitchToggle() {
//since this button is disabled performing click operation should do nothing
//and switch will be in checked state only
onView(withId(R.id.switch_data_collection)).perform(click())
onView(withId(R.id.switch_data_collection)).check(matches(isChecked()))

onView(withId(R.id.switch_data_collection)).perform(click())
onView(withId(R.id.switch_data_collection)).check(matches(isChecked()))
}

@Test
fun verifyDataAnalysisSwitchToggle() {
onView(withId(R.id.switch_data_analysis)).perform(click())
onView(withId(R.id.switch_data_analysis)).check(matches(not(isChecked())))

onView(withId(R.id.switch_data_analysis)).perform(click())
onView(withId(R.id.switch_data_analysis)).check(matches(isChecked()))
}

@Test
fun verifySaveSettingsButton() {
//button not shown on the basis of extras passed to intent
onView(withId(R.id.privacy_save_settings_btn)).check(matches(not(isDisplayed())))
//close the activity already open
activityRule.close()

//launch activity with extras as true
activityRule = launchActivity(getIntent(true))
//button will be shown if extras is true
onView(withId(R.id.privacy_save_settings_btn)).check(matches(isDisplayed()))
}

@After
fun tearDown() {
activityRule.close()
}
}
9 changes: 9 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,15 @@
android:name=".ui.preview.PreviewBitmapActivity"
android:exported="false"
android:theme="@style/Theme.ownCloud.OverlayGrey" />
<activity
android:name="com.nmc.android.ui.PrivacySettingsActivity"
android:exported="false"
android:windowSoftInputMode="stateAlwaysHidden" />

<activity
android:name="com.nmc.android.ui.LoginPrivacySettingsActivity"
android:exported="false"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name="com.nextcloud.client.documentscan.DocumentScanActivity"
android:exported="false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import com.nextcloud.ui.SetStatusDialogFragment;
import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
import com.nmc.android.ui.LauncherActivity;
import com.nmc.android.ui.LoginPrivacySettingsActivity;
import com.nmc.android.ui.PrivacySettingsActivity;
import com.owncloud.android.MainApp;
import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.authentication.DeepLinkLoginActivity;
Expand Down Expand Up @@ -470,6 +472,12 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract DocumentScanActivity documentScanActivity();

@ContributesAndroidInjector
abstract PrivacySettingsActivity privacySettingsActivity();

@ContributesAndroidInjector
abstract LoginPrivacySettingsActivity loginPrivacySettingsActivity();

@ContributesAndroidInjector
abstract GroupfolderListFragment groupfolderListFragment();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,24 @@ default void onDarkThemeModeChanged(DarkMode mode) {

void setCurrentAccountName(String accountName);

/**
* Saves the data analysis from privacy settings
* on disabling it we should disable Adjust SDK tracking
*
* @param enableDataAnalysis to enable/disable data analysis
*/
void setDataAnalysis(boolean enableDataAnalysis);
boolean isDataAnalysisEnabled();

/**
* Saves the privacy policy action taken by user
* this will maintain the state of current privacy policy action taken
* @see com.nmc.android.ui.LoginPrivacySettingsActivity for actions
* @param userAction taken by user
*/
void setPrivacyPolicyAction(int userAction);
int getPrivacyPolicyAction();

/**
* Gets status of migration to user id, default false
*
Expand Down
Loading

0 comments on commit 8f7c5be

Please sign in to comment.