Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable-3.28] Privacy #142

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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