diff options
author | cyberta <cyberta@riseup.net> | 2023-03-30 08:49:00 +0000 |
---|---|---|
committer | cyberta <cyberta@riseup.net> | 2023-03-30 08:49:00 +0000 |
commit | 49adad2fabcee3077be729064409bfcfbc99fe01 (patch) | |
tree | 6017e01def3ab93167d2e3ddcaad34aaec97a6ff /app | |
parent | 26c71bc66038924f07cfba2f578138acaf126b68 (diff) | |
parent | 93bb86626e68c9820b7f40eebfc71c959ed0f047 (diff) |
Merge branch 'fastlane' into 'master'
fastlane
See merge request leap/bitmask_android!241
Diffstat (limited to 'app')
15 files changed, 661 insertions, 62 deletions
diff --git a/app/build.gradle b/app/build.gradle index 07210528..5e5de9d0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -276,12 +276,13 @@ android { applicationIdSuffix ".beta" appSuffix = " Beta" buildConfigField "Boolean", "DEBUG_MODE", "true" + testCoverageEnabled = false // tor-android doesn't know this build-type, fallback to release in that case matchingFallbacks = ['release'] } debug { - testCoverageEnabled = true + testCoverageEnabled = false buildConfigField "Boolean", "DEBUG_MODE", "true" } } @@ -306,11 +307,6 @@ android { test { resources.srcDirs += ['src/test/resources'] - java.srcDirs += ['src/sharedTest/java'] - } - - androidTest { - java.srcDirs += ['src/sharedTest/java'] } fatweb { @@ -410,13 +406,17 @@ dependencies { testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.9' testImplementation group: 'com.tngtech.java', name: 'junit-dataprovider', version: '1.10.0' - androidTestImplementation 'org.mockito:mockito-core:3.6.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0' - //TODO: remove that library - androidTestImplementation 'com.jayway.android.robotium:robotium-solo:5.6.3' + androidTestImplementation 'org.mockito:mockito-core:3.9.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' + androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.0' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.0' + + androidTestImplementation 'tools.fastlane:screengrab:2.1.1' + testImplementation 'tools.fastlane:screengrab:2.1.1' + + testImplementation 'org.json:json:20180813' + androidTestImplementation 'androidx.test.ext:junit:1.1.4' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3' annotationProcessor 'com.squareup.dagger:dagger-compiler:1.2.2' diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderBaseTest.java b/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderBaseTest.java new file mode 100644 index 00000000..bbfcdc8b --- /dev/null +++ b/app/src/androidTest/java/se/leap/bitmaskclient/base/ProviderBaseTest.java @@ -0,0 +1,157 @@ +package se.leap.bitmaskclient.base; + +import static android.content.Context.MODE_PRIVATE; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.espresso.Espresso.onData; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.contrib.DrawerMatchers.isClosed; +import static androidx.test.espresso.matcher.RootMatchers.isDialog; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withTagValue; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.anything; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasToString; +import static org.hamcrest.Matchers.is; +import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; +import static utils.CustomInteractions.tryResolve; + +import android.content.SharedPreferences; +import android.view.Gravity; + +import androidx.test.espresso.DataInteraction; +import androidx.test.espresso.NoMatchingViewException; +import androidx.test.espresso.ViewInteraction; +import androidx.test.espresso.contrib.DrawerActions; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; + +import se.leap.bitmaskclient.R; +import tools.fastlane.screengrab.Screengrab; +import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy; +import tools.fastlane.screengrab.locale.LocaleTestRule; + +@LargeTest +@RunWith(AndroidJUnit4.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public abstract class ProviderBaseTest { + + @ClassRule + public static final LocaleTestRule localeTestRule = new LocaleTestRule(); + + @Rule + public ActivityScenarioRule<StartActivity> mActivityScenarioRule = + new ActivityScenarioRule<>(StartActivity.class); + + @Before + public void setup() { + Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy()); + SharedPreferences preferences = getApplicationContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + preferences.edit().clear().commit(); + } + + @Test + public void test01_vpnStartTest() throws InterruptedException { + boolean configurationNeeded = configureProviderIfNeeded(); + + ViewInteraction mainButtonStop; + if (!configurationNeeded) { + // click on Main on/off button and start VPN + ViewInteraction mainButton = tryResolve( + onView(withId(R.id.main_button)), + matches(isDisplayed()) + ); + + mainButton.perform(click()); + Thread.sleep(50); + Screengrab.screenshot("VPN_connecting"); + + mainButtonStop = tryResolve( + onView(allOf( + withId(R.id.button), + withTagValue(is("button_circle_stop")))), + matches(isDisplayed()), + 20); + Screengrab.screenshot("VPN_connected"); + } else { + // on new configurations the VPN is automatically started + Screengrab.screenshot("VPN_connecting"); + mainButtonStop = tryResolve( + onView(allOf( + withId(R.id.button), + withTagValue(is("button_circle_stop")))), + matches(isDisplayed()), + 20); + Screengrab.screenshot("VPN_connected"); + } + + mainButtonStop.perform(click()); + Screengrab.screenshot("VPN_ask_disconnect"); + + onView(withText(android.R.string.yes)) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + .perform(click()); + Screengrab.screenshot("VPN_disconnected"); + } + + @Test + public void test02_SettingsFragmentScreenshots() { + onView(withId(R.id.drawer_layout)) + .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed. + .perform(DrawerActions.open()); // Open Drawer + + Screengrab.screenshot("navigationDrawer"); + + // Start the screen of your activity. + onView(withId(R.id.advancedSettings)) + .perform(click()); + + Screengrab.screenshot("settingsFragment"); + } + + @Test + public void test03_LocationSelectionFragmentScreenshots() { + onView(withId(R.id.drawer_layout)) + .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed. + .perform(DrawerActions.open()); // Open Drawer + + onView(withId(R.id.manualGatewaySelection)) + .perform(click()); + + Screengrab.screenshot("GatewaySelectionFragment"); + } + + @Test + public void test04_AppExclusionFragmentScreenshots() { + onView(withId(R.id.drawer_layout)) + .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed. + .perform(DrawerActions.open()); // Open Drawer + + onView(withId(R.id.advancedSettings)).perform(click()); + + onView(withId(R.id.exclude_apps)).perform(click()); + + tryResolve( + onData(anything()).inAdapterView(withId(android.R.id.list)).atPosition(2), + matches(isDisplayed()), + 5); + + Screengrab.screenshot("App_Exclusion_Fragment"); + } + + public abstract boolean configureProviderIfNeeded(); +} diff --git a/app/src/androidTest/java/utils/CustomInteractions.java b/app/src/androidTest/java/utils/CustomInteractions.java new file mode 100644 index 00000000..9e3a8f9d --- /dev/null +++ b/app/src/androidTest/java/utils/CustomInteractions.java @@ -0,0 +1,82 @@ +package utils; + +import androidx.annotation.Nullable; +import androidx.test.espresso.DataInteraction; +import androidx.test.espresso.NoMatchingViewException; +import androidx.test.espresso.ViewAssertion; +import androidx.test.espresso.ViewInteraction; + +public class CustomInteractions { + + public static @Nullable + ViewInteraction tryResolve(ViewInteraction viewInteraction, int maxTries) { + return tryResolve(viewInteraction, null, maxTries); + } + + public static @Nullable + ViewInteraction tryResolve(ViewInteraction viewInteraction, ViewAssertion assertion) { + return tryResolve(viewInteraction, assertion, 10); + } + + public static @Nullable ViewInteraction tryResolve(ViewInteraction viewInteraction, ViewAssertion assertion, int maxTries) { + ViewInteraction resolvedViewInteraction = null; + int attempt = 0; + boolean hasFound = false; + while (!hasFound && attempt < maxTries) { + try { + resolvedViewInteraction = viewInteraction; + if (assertion != null) { + resolvedViewInteraction.check(assertion); + } + hasFound = true; + } catch (NoMatchingViewException exception) { + System.out.println("NoMatchingViewException attempt: " + attempt); + exception.printStackTrace(); + attempt++; + if (attempt == maxTries) { + throw exception; + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + break; + } + } + } + return resolvedViewInteraction; + } + + public static @Nullable + DataInteraction tryResolve(DataInteraction dataInteraction, ViewAssertion assertion, int maxTries) { + DataInteraction resolvedDataInteraction = null; + int attempt = 0; + boolean hasFound = false; + while (!hasFound && attempt < maxTries) { + try { + resolvedDataInteraction = dataInteraction; + if (assertion != null) { + resolvedDataInteraction.check(assertion); + } + hasFound = true; + } catch (Exception exception) { + // TODO: specify expected exception + attempt++; + if (attempt == maxTries) { + throw exception; + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + break; + } + } + } + return resolvedDataInteraction; + } + public static @Nullable + DataInteraction tryResolve(DataInteraction dataInteraction, int maxTries) { + return tryResolve(dataInteraction, null, maxTries); + } +} diff --git a/app/src/androidTest/java/utils/CustomMatchers.java b/app/src/androidTest/java/utils/CustomMatchers.java new file mode 100644 index 00000000..5fe11cd2 --- /dev/null +++ b/app/src/androidTest/java/utils/CustomMatchers.java @@ -0,0 +1,31 @@ +package utils; + +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +public class CustomMatchers { + + public static Matcher<View> childAtPosition( + final Matcher<View> parentMatcher, final int position) { + + return new TypeSafeMatcher<View>() { + @Override + public void describeTo(Description description) { + description.appendText("Child at position " + position + " in parent "); + parentMatcher.describeTo(description); + } + + @Override + public boolean matchesSafely(View view) { + ViewParent parent = view.getParent(); + return parent instanceof ViewGroup && parentMatcher.matches(parent) + && view.equals(((ViewGroup) parent).getChildAt(position)); + } + }; + } +} diff --git a/app/src/androidTest/java/utils/CustomViewActions.java b/app/src/androidTest/java/utils/CustomViewActions.java new file mode 100644 index 00000000..fb0dcc27 --- /dev/null +++ b/app/src/androidTest/java/utils/CustomViewActions.java @@ -0,0 +1,89 @@ +package utils; + +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +import android.view.View; + +import androidx.annotation.StringRes; +import androidx.test.espresso.UiController; +import androidx.test.espresso.ViewAction; +import androidx.test.espresso.util.TreeIterables; + +import org.hamcrest.Matcher; + +public class CustomViewActions { + + public static ViewAction waitForView(int viewId, long timeout) { + return new ViewAction() { + + @Override + public String getDescription() { + return "Wait for view with specific id \n@param viewId: resource ID \n@param timeout: timeout in milli seconds."; + } + + @Override + public Matcher<View> getConstraints() { + return isRoot(); + } + + @Override + public void perform(UiController uiController, View view) { + uiController.loopMainThreadUntilIdle(); + long startTime = System.currentTimeMillis(); + long endTime = startTime + timeout; + Matcher viewMatcher = withId(viewId); + + while (System.currentTimeMillis() < endTime) { + // Iterate through all views on the screen and see if the view we are looking for is there already + for (View child : TreeIterables.breadthFirstViewTraversal(view)) { + // found view with required ID + if (viewMatcher.matches(child)) { + return; + } + } + // Loops the main thread for a specified period of time. + // Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again. + uiController.loopMainThreadForAtLeast(100); + } + } + }; + } + + public static ViewAction waitForText(@StringRes int textId, long timeout) { + return new ViewAction() { + + @Override + public String getDescription() { + return "Wait for view with specific id \n@param viewId: resource ID \n@param timeout: timeout in milli seconds."; + } + + @Override + public Matcher<View> getConstraints() { + return isRoot(); + } + + @Override + public void perform(UiController uiController, View view) { + uiController.loopMainThreadUntilIdle(); + long startTime = System.currentTimeMillis(); + long endTime = startTime + timeout; + Matcher viewMatcher = withText(textId); + + while (System.currentTimeMillis() < endTime) { + // Iterate through all views on the screen and see if the view we are looking for is there already + for (View child : TreeIterables.breadthFirstViewTraversal(view)) { + // found view with required ID + if (viewMatcher.matches(child)) { + return; + } + } + // Loops the main thread for a specified period of time. + // Control may not return immediately, instead it'll return after the provided delay has passed and the queue is in an idle state again. + uiController.loopMainThreadForAtLeast(100); + } + } + }; + } +}
\ No newline at end of file diff --git a/app/src/androidTestCustom/java/se/leap/bitmaskclient/base/CustomProviderTest.java b/app/src/androidTestCustom/java/se/leap/bitmaskclient/base/CustomProviderTest.java new file mode 100644 index 00000000..1a0814b6 --- /dev/null +++ b/app/src/androidTestCustom/java/se/leap/bitmaskclient/base/CustomProviderTest.java @@ -0,0 +1,49 @@ +package se.leap.bitmaskclient.base; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.RootMatchers.isDialog; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static utils.CustomInteractions.tryResolve; + +import androidx.test.espresso.ViewInteraction; + +import org.junit.Test; + +import se.leap.bitmaskclient.R; +import tools.fastlane.screengrab.Screengrab; + +public class CustomProviderTest extends ProviderBaseTest { + + @Test + @Override + public void test01_vpnStartTest() throws InterruptedException { + ViewInteraction mainButtonStop; + mainButtonStop = tryResolve( + onView(withId(R.id.main_button)), + matches(isDisplayed()), + 30); + Screengrab.screenshot("VPN_connected"); + + mainButtonStop.perform(click()); + Screengrab.screenshot("VPN_ask_disconnect"); + + ViewInteraction alertDialogOKbutton = tryResolve(onView(withText(android.R.string.yes)) + .inRoot(isDialog()), + matches(isDisplayed())); + alertDialogOKbutton.perform(click()); + Screengrab.screenshot("VPN_disconnected"); + + mainButtonStop.perform(click()); + Thread.sleep(50); + Screengrab.screenshot("VPN_connecting"); + } + + @Override + public boolean configureProviderIfNeeded() { + return false; + } +}
\ No newline at end of file diff --git a/app/src/androidTestCustom/java/se/leap/bitmaskclient/suite/ScreenshotTest.java b/app/src/androidTestCustom/java/se/leap/bitmaskclient/suite/ScreenshotTest.java new file mode 100644 index 00000000..a19b0ffd --- /dev/null +++ b/app/src/androidTestCustom/java/se/leap/bitmaskclient/suite/ScreenshotTest.java @@ -0,0 +1,17 @@ +package se.leap.bitmaskclient.suite; + + +import androidx.test.filters.LargeTest; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import se.leap.bitmaskclient.base.CustomProviderTest; + +@LargeTest +@RunWith(Suite.class) +@Suite.SuiteClasses({ + CustomProviderTest.class +}) +public class ScreenshotTest { +} diff --git a/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/BitmaskTest.java b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/BitmaskTest.java new file mode 100644 index 00000000..aa437c74 --- /dev/null +++ b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/BitmaskTest.java @@ -0,0 +1,42 @@ +package se.leap.bitmaskclient.base; + + +import static androidx.test.espresso.Espresso.onData; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasToString; +import static utils.CustomInteractions.tryResolve; + +import androidx.test.espresso.DataInteraction; +import androidx.test.espresso.NoMatchingViewException; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import org.junit.FixMethodOrder; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; + +import se.leap.bitmaskclient.R; + +@LargeTest +@RunWith(AndroidJUnit4.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class BitmaskTest extends ProviderBaseTest { + + @Override + public boolean configureProviderIfNeeded() { + try { + DataInteraction linearLayout = tryResolve(onData(hasToString(containsString("riseup.net"))) + .inAdapterView(withId(R.id.provider_list)), + 2); + linearLayout.perform(click()); + return true; + } catch (NoMatchingViewException e) { + // it might be that the provider was already configured, so we print the stack + // trace here and try to continue + e.printStackTrace(); + } + return false; + } +} diff --git a/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/ProviderSetupTest.java b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/ProviderSetupTest.java new file mode 100644 index 00000000..23db8582 --- /dev/null +++ b/app/src/androidTestNormal/java/se/leap/bitmaskclient/base/ProviderSetupTest.java @@ -0,0 +1,68 @@ +package se.leap.bitmaskclient.base; + + +import static android.content.Context.MODE_PRIVATE; +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static androidx.test.espresso.Espresso.onData; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static org.hamcrest.Matchers.anything; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasToString; +import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; +import static utils.CustomInteractions.tryResolve; + +import android.content.SharedPreferences; + +import androidx.test.espresso.DataInteraction; +import androidx.test.espresso.NoMatchingViewException; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.providersetup.ProviderListActivity; +import tools.fastlane.screengrab.Screengrab; +import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy; +import tools.fastlane.screengrab.locale.LocaleTestRule; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class ProviderSetupTest { + + @ClassRule + public static final LocaleTestRule localeTestRule = new LocaleTestRule(); + + @Rule + public ActivityScenarioRule<ProviderListActivity> mActivityScenarioRule = + new ActivityScenarioRule<>(ProviderListActivity.class); + + @Before + public void setup() { + Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy()); + SharedPreferences preferences = getApplicationContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + preferences.edit().clear().commit(); + } + + @Test + public void testConfigureRiseupVPNScreenshot() { + DataInteraction linearLayout = tryResolve(onData(hasToString(containsString("riseup.net"))) + .inAdapterView(withId(R.id.provider_list)), + 2); + Screengrab.screenshot("ProviderListActivity"); + linearLayout.perform(click()); + Screengrab.screenshot("ProviderListActivity_configureRiseup"); + } + + @Test + public void testaddManuallyNewProviderScreenshot() { + onData(anything()).inAdapterView(withId(R.id.provider_list)).atPosition(3).perform(click()); + Screengrab.screenshot("ProviderListActivity_addManuallyNewProvider"); + } +} diff --git a/app/src/androidTestNormal/java/se/leap/bitmaskclient/suite/ScreenshotTest.java b/app/src/androidTestNormal/java/se/leap/bitmaskclient/suite/ScreenshotTest.java new file mode 100644 index 00000000..5fa45a95 --- /dev/null +++ b/app/src/androidTestNormal/java/se/leap/bitmaskclient/suite/ScreenshotTest.java @@ -0,0 +1,19 @@ +package se.leap.bitmaskclient.suite; + + +import androidx.test.filters.LargeTest; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import se.leap.bitmaskclient.base.ProviderSetupTest; +import se.leap.bitmaskclient.base.BitmaskTest; + +@LargeTest +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ProviderSetupTest.class, + BitmaskTest.class, +}) +public class ScreenshotTest { +} diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..51e17015 --- /dev/null +++ b/app/src/debug/AndroidManifest.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2011 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="se.leap.bitmaskclient" + android:requestLegacyExternalStorage="true" + > + <!-- package is overwritten in build.gradle --> + + <!-- The following permissions are required by fastlane / espresso --> + <!-- _____________________________________________________________ --> + <!-- Allows unlocking your device and activating its screen so UI tests can succeed --> + <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/> + <uses-permission android:name="android.permission.WAKE_LOCK"/> + + <!-- Allows for storing and retrieving screenshots --> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" + tools:ignore="ScopedStorage" /> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + + <!-- Allows changing locales --> + <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" + tools:ignore="ProtectedPermissions" /> + + <uses-permission android:name="android.permission.DUMP" + tools:ignore="ProtectedPermissions" /> + <!-- _____________________________________________________________ --> + +</manifest> diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java index 68435fec..aa894cca 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java @@ -424,11 +424,11 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider(); account.setText(currentProvider.getName()); initManualGatewayEntry(); - } + } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(PREFERRED_CITY)) { + if (key != null && key.equals(PREFERRED_CITY)) { initManualGatewayEntry(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java b/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java index fc86fc0b..c7273613 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java @@ -48,10 +48,12 @@ public class MainButton extends RelativeLayout { public void updateState(boolean isOn, boolean isProcessing) { if (isProcessing) { button.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.button_circle_cancel)); + button.setTag("button_circle_cancel"); } else { button.setImageDrawable( ContextCompat.getDrawable(getContext(), isOn ? R.drawable.button_circle_stop : R.drawable.button_circle_start)); + button.setTag(isOn ? "button_circle_stop" : "button_circle_start"); } } } diff --git a/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java index 72791ba6..72791ba6 100644 --- a/app/src/sharedTest/java/se.leap.bitmaskclient/testutils/TestSetupHelper.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java diff --git a/app/src/test/resources/v4/riseup.net.cert b/app/src/test/resources/v4/riseup.net.cert index 1beff4bd..2708a8c8 100644 --- a/app/src/test/resources/v4/riseup.net.cert +++ b/app/src/test/resources/v4/riseup.net.cert @@ -1,54 +1,54 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAqSkd5UbKh+HbWSH603MJNgSkomeBnPooE0dlxYXIOQa/HnTl -5+7CV47JDWjFWSj1h+zeAmXcgkQxDRF1rbZ+oov26SYyGingBQgn9qvoCZwIdesE -N5Xhdvt23FK8edanCMN/tn1CnyaO5xvHdWRvhpmEf9zAsVwo9qfx1vmRsJfzvcnH -rHoCgH3nxzK1VZm5xNGRdAm9jA2odYYIuSKN1hSYgjcnXVvNHm3bWs+B4RzgtrZG -vy+vsSSd6AaYGQ4sCj3DOuKNOWqXZ8yFpfjbi0rYsOmzxEFiZiequxhrafSM+2uy -NbdPsPXKd0veZh1ApDEHW/6I+qP0bnHE6Dtr+wIDAQABAoIBABnwxjbcrj48Mmju -vwoh/+2atKx69vNdoTujnUW3CEdGc5R2FLOGd6L5sHcv8+OCVnSrrDft6uzHDEaW -wNcMv0qp8Ak85D4C4emjoI1BO2oN1XZPvevQPi0Czu1meqSseBzt7e3MM6U4Qn3K -UsH7zuZzMFBzR9Fq8pUwl/OBfgf4ZWF0IeHFx1/T+3A/lCTdki2wZ8M7pN7djED1 -fqmbwXt3KSnhhOAjZm17qhVM6K+kA3EInsijShbeUTtQMyZCifIrtj5EHYf/sr3f -mlXdspIaR5Wh5tKQo9TRrBNgmMxg+GhBz7fwaM2P1uZXVWTAK4L35vz17o0AQR0P -aItWxyECgYEA3fEGWgH53tATZklxiXaEInCve+XNpE2m80g1hNkIS36D676e0Mxm -L1PlgO+YDRgLBAQVbSdUbfQrcc8Zlcn7R2a0X1a12OH/4pKFXNM4doS/3k5wN1TI -Zr7+AqP4vaLVNt8fjYBhRYpOdwpr5PTjJCpP9dh3ItuIOyH/w+2k4iMCgYEAwx6Y -Cjkn7IaBJOr0kK5JORlGx10jfUmMLMiaJATs49vAcgxt7rKTiEUNvS1YArR4Vh/n -hAYmsC76folGTeZjotmzQbUkm/yZ3OMTSrcja85uM7NJbt9CnUPiDkKDEY4Exhuh -K/Ls+Lp6/eIyJ+b4/xpFEzlUQyY4WiZ6UWcEUEkCgYEAxsqVYtd0RQPg7HSKMpMq -RWLje7lZWXqIOE6MSWLQUDaQ2P6TZ/g86tVdswBoFApeC4nQ20UoFZhntXfHtegF -n225z89t8EZ1mS6eL4etglLjPK7LSnQxT/5wrFLMgKcyDQULUQYVmmEIaQ23mItU -TFdt6YmrJFi4jCam3YqlbjsCgYACzTSnqOxu0/uUuR7r2OTKQhenEypIST8PAY5d -CAkSuHwJ5y3I6J1/rmYlGjqSR18W9XxQg/oYO4RzPqtYwP8bPn75aY1uA/F9n3EO -eJS0npEsgt2CDwiY03mydLgHD3/4DDuDMwi+BYdwj8filMlseEcXoJIaKLlUagsF -kjIYqQKBgEDDiM51YU2V1k4rzdzICdfo5mqa7FiQ2JiRs3yG20gNg3nDReQZk0Q4 -poJkZksuojQabDOnHzWc2jfUehll6gC1ijtRzaoSeRH2m8X5JXb3vfAe5/TFZHBg -bErDqPo+En9Y72LIcGG5vlzQHfE0l24C0oQ64cssLHA9+VnYmwhw +MIIEpAIBAAKCAQEA29qrZxVQz0crl9USbBpzZCMfbDno8tZVjI/imqLgdfgmaW2D +5YEmDMPrr3GEqVBKAYT9LHRgmTSi63PW3N8gb9CKMdoQtSL35XcLbXgtdsJEzSSy +CbtXmB4Yrp2NhzMS9qhFAjbLjjnZkaECLmZDka5dy6j6JWjRNlpE4umqGSCzBtaA +IXuUjMF3hyZ9kY2ojdRDSo3I/gebo2vwaUj/lwYbs1lkDf0LVCcwmpLkppxnf+Cg +T8MA9HRvOk8TZJpShAPWWOBNWVjh79OF41MCbIAr9ynrNR8grOTJV57q1V04/Gi+ +Ue0BkvxsGtNGxTrS4lC672a3Z8nW/NCuMnVTHwIDAQABAoIBAQC8jGuFK32zVlkn +jL+Q4JpnncucGIoUgQa7VsbDUb5ozdm7fwWn9Tu5pOji/NsGDep6JSCvWFtj6QV0 +IlN59w2td06ddGPxxLyPGao+RtvOxssUmEzsFbQIrH8EefBfq8iuqx8LyAyIvEpA +H7JsMp3uOXkNaaymGp+aGo6LgFO12YMNq4OzGeyvetqwkFNkowYOLzAXa99MruZA +xl2lJ4Pm3P7sQspUOxaWrSUYImcg0yM9vpDPxn46f1N3voNGXc2v0okizCOIyrk9 +GF8BHZra8v5oinReCD0by7Xg4X2OfMuOV46lN/TlZ5kmNmsPxORMpuu4NkhAyGRj +dPEU/rHxAoGBAPcTKQk3HZoOf+z/ax7OpLvt+qkWuQrKzjKa+AfiE6cqiLG3oN9I +5qhZYI9vFszEw0Ak5Oouc2EWv1/m0Um/w8CQdbeH0AvxQZDKKLdothDwxokVB0fQ +Ish34lZyHrccsF96GzZMmepjYqiaebsv0ptkn1a0Umc87ao90UdDUL6nAoGBAOPL +yNLADh3LlY2FUVztDG4We2d6Wjr+SI9H4SShXZhz6067I+TwWeXl4mtPAUhAr6ex +/TLE+cRvq8SxDPiKSu4h2UKm5h0HMSpCbFQaqOnPgYihwSTm8/C9uKr2e9mqNSsw +VJYyO1Gbrsbb7NOZFmm9uivg9X5aMwF/1wWQGY7JAoGABoDVmq19tPleuqE6c5Qi +1+N6roqvki4mYUSc9LAprkO7V1oq/NWRZKr9lKjq47bmIMEX2WYhmVOc8+xCY/uN +Lnte7dbATiAqhqIbkkBKUoXT4/XOvEApOjeVmIrmbhFuPwUaxEId5wJ4rVFrlNa8 +Z2StoP2cEaWT5+A6qvKFpI8CgYEAx/N6pbM7MOAguAaL8puIy6EkVSJKzXmiy1H2 +yCZ0d3tY0tTlnvFyl5//7N1+bKOLDBHqBIRuEQVMquwWTJtnRjuj7yN83YIQn92K +JRD5r7IbK4mAdhnbijeeP0L4V4lV/kEAHo6dDvcupRMqgFniGJMXNajTFEOsfeZv +IUzpgjECgYAj4L4boXvCmZIkI46MU9JxQk0eHusR2uGwpdrMiokIsz7uN+MogK1U +1ijIZp/tDH9W1K4t2sTrmRXBee8bZ6iIvj5qPFJqmj+uil20UbyCPQvpAxHn+VNt +gUOLvqlGa2uQ9aymKjnG9bmg7Uk8qXIFsQf2DUYLwrmJjsF6cI2xig== -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIIEmzCCAoOgAwIBAgIQejc1yqfKGehUKDMdybDH+DANBgkqhkiG9w0BAQsFADB1 +MIIEmzCCAoOgAwIBAgIQcAUo8ZeIKRBNyIAyEg7QFDANBgkqhkiG9w0BAQsFADB1 MRgwFgYDVQQKDA9SaXNldXAgTmV0d29ya3MxGzAZBgNVBAsMEmh0dHBzOi8vcmlz ZXVwLm5ldDE8MDoGA1UEAwwzUmlzZXVwIE5ldHdvcmtzIFJvb3QgQ0EgKGNsaWVu -dCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTIyMTAxNDAwMDAwMFoXDTIzMDExNDAw -MDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEMTMxaXF4cHB0cHljYnN3eGRueXRu -aHhiMDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkpHeVGyofh21kh -+tNzCTYEpKJngZz6KBNHZcWFyDkGvx505efuwleOyQ1oxVko9Yfs3gJl3IJEMQ0R -da22fqKL9ukmMhop4AUIJ/ar6AmcCHXrBDeV4Xb7dtxSvHnWpwjDf7Z9Qp8mjucb -x3Vkb4aZhH/cwLFcKPan8db5kbCX873Jx6x6AoB958cytVWZucTRkXQJvYwNqHWG -CLkijdYUmII3J11bzR5t21rPgeEc4La2Rr8vr7EknegGmBkOLAo9wzrijTlql2fM -haX424tK2LDps8RBYmYnqrsYa2n0jPtrsjW3T7D1yndL3mYdQKQxB1v+iPqj9G5x -xOg7a/sCAwEAAaNvMG0wHQYDVR0OBBYEFEFB2IaA9Z5TOAPtMw3vgKvRR+tpMAsG +dCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTIyMTIyMDAwMDAwMFoXDTIzMDMyMDAw +MDAwMFowLTErMCkGA1UEAwwiVU5MSU1JVEVEYnpnMWZicmd3MWdsa29qMWlhZG4w +d2ZiaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANvaq2cVUM9HK5fV +Emwac2QjH2w56PLWVYyP4pqi4HX4Jmltg+WBJgzD669xhKlQSgGE/Sx0YJk0outz +1tzfIG/QijHaELUi9+V3C214LXbCRM0ksgm7V5geGK6djYczEvaoRQI2y4452ZGh +Ai5mQ5GuXcuo+iVo0TZaROLpqhkgswbWgCF7lIzBd4cmfZGNqI3UQ0qNyP4Hm6Nr +8GlI/5cGG7NZZA39C1QnMJqS5KacZ3/goE/DAPR0bzpPE2SaUoQD1ljgTVlY4e/T +heNTAmyAK/cp6zUfIKzkyVee6tVdOPxovlHtAZL8bBrTRsU60uJQuu9mt2fJ1vzQ +rjJ1Ux8CAwEAAaNvMG0wHQYDVR0OBBYEFF1cHd5fjHXKbGrW0qd4zNg6KquiMAsG A1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAJBgNVHRMEAjAAMB8GA1Ud -IwQYMBaAFBf0G9XlKgEBTWuiXTYKKQmWZYBGMA0GCSqGSIb3DQEBCwUAA4ICAQB0 -x+RhbfmWuKQ7clT2oBhLOep1USJ9y98BLMsEmqZ84q0CnG+tI2QLhZvaMiGiMplI -saHhK1lIHbO/UATHOhE+ZbY9vAaGK1JaBdlOTshjo+cijZ2nWwfwa9Qj/Cx64sSO -cee3Lbg5IcsFc/0KGCTw0k9PheD/CudRSnLCxImA8DmJO+1w1NUoBBm48tdD6usD -fLU35FvChvNwW/7GO/q3TGXF/jrreBnrJZMjsffwFOJC8SWZIAScmF/OrqWyruYb -ZS9/0ZXR21TT1mvUFjIs+0uY2crfli/f91ZbB9eRoiyHkck8RtotiH9PrmYt7j02 -5cwos2BqRs+kZ7AgYvZjWErwIyCsXSzrk0WztLa2vslqZSZSt12VlW5iIyKTyx8T -snQLEdHwzRT5C+9daq3PneOvD5KhgyySlwWSoMuJdg2n3cTs/0uMDQE7FIim11XM -Fz8liZf4PcWv8YLFUBHQ367SvbAiyZLzpXZETQyDaYNYGTwYBCGs9Yhq3KM85Kmz -f0rgrBpm61ujLQ/OBSCq6/RA9BN8UYdO+YS0vengYtIdc+aw1PzLhcC9dLLXC4ef -zH25zfe7vkt58UcuG+YCsaIR8Hy3I/yDQWVkbNawEYUboEHHRd8pQmUIkHLaYibt -eq1SXtuulUUDIkyfRATiA/GkPVPPnJImL8XK12WFPQ== ------END CERTIFICATE----- +IwQYMBaAFBf0G9XlKgEBTWuiXTYKKQmWZYBGMA0GCSqGSIb3DQEBCwUAA4ICAQCS +sDwNJmNqgrOeNgEuszIGE5V2wTj89S2TtHggBfWErKGUn7N0+gcIRvxxCHG9X7ne +WjhTS/kEMblR3OcaB/8kfBGCZD6Hc+d0nXv7G5Iqpy5eJuWnv8L4n7QUxIkYiyRr +bp3Mg31MCFKlDVi2RoTbm3rvcWYPsfBARu6tZlvfNb2Q6dP9/zPU1N2b69fyNwXV +IOjF/5KPt4n/abxyFuty6xv1G9B3Bq072wZrUSQbZsOLNUtSyLJVyhHRLBBzOOJ/ +Cw8awAEeXXwF1+qAO4MAq/ILcA/v8hAR6AlZez6nbZOBfk5JvEPumZPdIQN6PQ6J +jODFuJRFFemea3uYyUEnv5MD3BcsApIrM7eRc82Edc9a73CkuMP4ekG68OFl2qHl +h+XzEchQNVG3t+Ka+8FN1RyeXhPRjzf1JG0elP+CIuyHBOd1SJLv4elGK2ZJWk2L +cPfLt+tFATKy3PEuftXasLG9g/K8PVaNh8gmh4en88vM0XxZ7npygPv84Cl5wV4r +LUuR6Cy8quE8QHx01D2EAspkHqk4d/xClyxfT1KCKEQvOk5gOlSrEFsB8TTqtYfr +G1LiVDcwEq+ExmeDwGIW0EY2mOre7yTHva0p0JZ4AcyK4lNIPXpGBOQcyZ++7/jh +vdNyERG7ZAjCsj7YTy72On5cS6DQDEHnKmrAIejLLw== +-----END CERTIFICATE-----
\ No newline at end of file |