diff options
Diffstat (limited to 'app')
35 files changed, 1274 insertions, 4475 deletions
diff --git a/app/build.gradle b/app/build.gradle index b46ed9ab..a809e86a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,8 +6,8 @@ apply plugin: 'com.android.application' def appName = 'Bitmask' android { - compileSdkVersion 27 - buildToolsVersion '27.0.3' + compileSdkVersion 28 + buildToolsVersion '28.0.3' compileOptions { targetCompatibility 1.8 @@ -153,43 +153,44 @@ android { dependencies { testImplementation 'junit:junit:4.12' + //outdated mockito-core version due to powermock dependency testImplementation 'org.mockito:mockito-core:2.8.9' testImplementation('org.powermock:powermock-api-mockito2:1.7.3') { exclude group: 'junit' exclude group: 'org.mockito' } testImplementation 'org.powermock:powermock-module-junit4:1.7.3' testImplementation 'org.powermock:powermock-core:1.7.3' testImplementation 'org.powermock:powermock-module-junit4-rule:1.7.3' + testImplementation group: 'com.tngtech.java', name: 'junit-dataprovider', version: '1.10.0' androidTestImplementation 'org.mockito:mockito-core:2.8.9' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' - androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.1' - androidTestImplementation 'com.android.support.test.espresso:espresso-intents:3.0.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-intents:3.0.2' //TODO: remove that library androidTestImplementation 'com.jayway.android.robotium:robotium-solo:5.6.3' testImplementation 'junit:junit:4.12' testImplementation 'org.json:json:20170516' - debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4' - releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' - betaImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2' + releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2' + betaImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2' annotationProcessor 'com.jakewharton:butterknife:6.1.0' annotationProcessor 'com.squareup.dagger:dagger-compiler:1.2.2' implementation 'com.jakewharton:butterknife:6.1.0' //TODO: replace that library compileOnly 'com.squareup.dagger:dagger-compiler:1.2.2' - //TODO: remove that library? implementation 'com.github.pedrovgs:renderers:1.5' implementation 'com.intellij:annotations:12.0' implementation 'com.google.code.gson:gson:2.8.2' implementation 'com.squareup.okhttp3:okhttp:3.9.0' - implementation "com.android.support:support-core-utils:27.0.2" - implementation 'com.android.support:support-annotations:27.0.2' - implementation 'com.android.support:support-v4:27.0.2' - implementation 'com.android.support:appcompat-v7:27.0.2' - implementation 'com.android.support:design:27.0.2' - implementation 'com.android.support:support-fragment:27.0.2' - implementation 'com.android.support.constraint:constraint-layout:1.0.2' - implementation 'com.android.support:multidex:1.0.2' - implementation 'com.android.support:cardview-v7:27.0.2' + implementation "com.android.support:support-core-utils:28.0.0" + implementation 'com.android.support:support-annotations:28.0.0' + implementation 'com.android.support:support-v4:28.0.0' + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:design:28.0.0' + implementation 'com.android.support:support-fragment:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:multidex:1.0.3' + implementation 'com.android.support:cardview-v7:28.0.0' } // Ensure the no-op dependency is always used in JVM tests. @@ -201,12 +202,23 @@ configurations.all { config -> } } } - resolutionStrategy.force "com.android.support:support-annotations:27.0.2" - resolutionStrategy.force "com.android.support:support-v4:27.0.2" - resolutionStrategy.force "com.android.support:support-core-utils:27.0.2" - resolutionStrategy.force "com.android.support:appcompat-v7:27.0.2" - resolutionStrategy.force "com.android.support:design:27.0.2" - resolutionStrategy.force "com.android.support:support-fragment:27.0.2" + resolutionStrategy.force "com.android.support:support-annotations:28.0.0" + resolutionStrategy.force "com.android.support:support-v4:28.0.0" + resolutionStrategy.force "com.android.support:support-core-utils:28.0.0" + resolutionStrategy.force "com.android.support:appcompat-v7:28.0.0" + resolutionStrategy.force "com.android.support:design:28.0.0" + resolutionStrategy.force "com.android.support:support-fragment:28.0.0" +} + +subprojects { + afterEvaluate {project -> + if (project.hasProperty("android")) { + android { + compileSdkVersion 28 + buildToolsVersion "28.0.3" + } + } + } } def processFileInplace(file, Closure processText) { @@ -216,31 +228,31 @@ def processFileInplace(file, Closure processText) { task copyIcsOpenVPNClasses( type: Copy ) { println "copyIcsOpenVPNClasses" - from ('../ics-openvpn/main/') { - include '**/LaunchVPN.java' - include '**/OpenVPNSservice.java' - include '**/VpnProfile.java' - include '**/DisconnectVPN.java' - include '**/VpnProfile.java' - include '**/SeekBarTicks.java' - include '**/core/**.java' - include '**/activities/BaseActivity.java' - include '**/APIVpnProfile.java' - include '**/aidl/**/api/**.aidl' - include '**/aidl/**/core/**.aidl' - - includeEmptyDirs = false - - filter { - line -> line.replaceAll('de.blinkt.openvpn.R', 'se.leap.bitmaskclient.R') - } - filter { - line -> line.replaceAll('de.blinkt.openvpn.BuildConfig', 'se.leap.bitmaskclient.BuildConfig') - } - filter { - line -> line.replace('package de.blinkt.openvpn;', 'package de.blinkt.openvpn;\n\nimport se.leap.bitmaskclient.R;') - } - } into '.' +// from ('../ics-openvpn/main/') { +// include '**/LaunchVPN.java' +// include '**/OpenVPNSservice.java' +// include '**/VpnProfile.java' +// include '**/DisconnectVPN.java' +// include '**/VpnProfile.java' +// include '**/SeekBarTicks.java' +// include '**/core/**.java' +// include '**/activities/BaseActivity.java' +// include '**/APIVpnProfile.java' +// include '**/aidl/**/api/**.aidl' +// include '**/aidl/**/core/**.aidl' +// +// includeEmptyDirs = false +// +// filter { +// line -> line.replaceAll('de.blinkt.openvpn.R', 'se.leap.bitmaskclient.R') +// } +// filter { +// line -> line.replaceAll('de.blinkt.openvpn.BuildConfig', 'se.leap.bitmaskclient.BuildConfig') +// } +// filter { +// line -> line.replace('package de.blinkt.openvpn;', 'package de.blinkt.openvpn;\n\nimport se.leap.bitmaskclient.R;') +// } +// } into '.' } task copyIcsOpenVPNXml( type: Copy ) { @@ -262,31 +274,31 @@ task copyIcsOpenVPNXml( type: Copy ) { task copyIcsOpenVPNImages( type: Copy ) { println "copyIcsOpenVPNImages" - from ('../ics-openvpn/main/') { - include '**/ic_filter*.png' - include '**/ic_delete*.png' - include '**/ic_share*.png' - include '**/ic_close*.png' - include '**/ic_edit*.png' - include '**/ic_check*.png' - include '**/ic_pause*.png' - include '**/ic_play*.png' - include '**/ic_content_copy_white_*.png' - include '**/ic_add_circle_outline_white_*.png' - include '**/ic_warning_black_*.png' - include '**/ic_add_circle_outline_grey600_*.png' - include '**/ic_archive_grey600_*.png' - include '**/ic_receipt_white_*.png' - include '**/ic_sort_white_*.png' - include '**/ic_content_copy_white_*.png' - include '**/ic_archive_white_*.png' - include '**/ic_menu_archive*.png' - include '**/vpn_item_settings*.png' - include '**/ic_menu_log*.png' - include '**/ic_menu_copy_holo_light*.png' - - includeEmptyDirs = false - } into '.' +// from ('../ics-openvpn/main/') { +// include '**/ic_filter*.png' +// include '**/ic_delete*.png' +// include '**/ic_share*.png' +// include '**/ic_close*.png' +// include '**/ic_edit*.png' +// include '**/ic_check*.png' +// include '**/ic_pause*.png' +// include '**/ic_play*.png' +// include '**/ic_content_copy_white_*.png' +// include '**/ic_add_circle_outline_white_*.png' +// include '**/ic_warning_black_*.png' +// include '**/ic_add_circle_outline_grey600_*.png' +// include '**/ic_archive_grey600_*.png' +// include '**/ic_receipt_white_*.png' +// include '**/ic_sort_white_*.png' +// include '**/ic_content_copy_white_*.png' +// include '**/ic_archive_white_*.png' +// include '**/ic_menu_archive*.png' +// include '**/vpn_item_settings*.png' +// include '**/ic_menu_log*.png' +// include '**/ic_menu_copy_holo_light*.png' +// +// includeEmptyDirs = false +// } into '.' } // thanks to http://pleac.sourceforge.net/pleac_groovy/fileaccess.html diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/StartActivityDrawerTest.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/StartActivityDrawerTest.java index d7580c88..2d3fe6bb 100644 --- a/app/src/androidTest/java/se/leap/bitmaskclient/test/StartActivityDrawerTest.java +++ b/app/src/androidTest/java/se/leap/bitmaskclient/test/StartActivityDrawerTest.java @@ -15,8 +15,10 @@ import org.junit.runner.RunWith; import java.io.IOException; +import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.MainActivity; import se.leap.bitmaskclient.Provider; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.testutils.TestSetupHelper; import static android.support.test.InstrumentationRegistry.getInstrumentation; @@ -26,17 +28,21 @@ import static android.support.test.espresso.action.ViewActions.click; import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.contrib.DrawerMatchers.isClosed; import static android.support.test.espresso.contrib.DrawerMatchers.isOpen; +import static android.support.test.espresso.matcher.RootMatchers.isDialog; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.anything; +import static se.leap.bitmaskclient.Constants.FIRST_TIME_USER_DATE; +import static se.leap.bitmaskclient.Constants.LAST_DONATION_REMINDER_DATE; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT; import static se.leap.bitmaskclient.R.id.aboutLayout; import static se.leap.bitmaskclient.R.id.accountList; -import static se.leap.bitmaskclient.R.id.provider_list_layout; import static se.leap.bitmaskclient.R.id.drawer_layout; import static se.leap.bitmaskclient.R.id.eipServiceFragment; import static se.leap.bitmaskclient.R.id.log_layout; +import static se.leap.bitmaskclient.R.id.provider_list_layout; import static se.leap.bitmaskclient.R.id.settingsList; /** @@ -65,7 +71,8 @@ public class StartActivityDrawerTest { preferencesEditor = preferences.edit(); preferencesEditor.putString(Provider.KEY, TestSetupHelper.getInputAsString(InstrumentationRegistry.getContext().getAssets().open("riseup.net.json"))) .putString(Provider.CA_CERT, TestSetupHelper.getInputAsString(InstrumentationRegistry.getContext().getAssets().open("riseup.net.pem"))) - .commit(); + .putString(LAST_DONATION_REMINDER_DATE, null) + .putString(FIRST_TIME_USER_DATE, null).commit(); } @@ -89,39 +96,86 @@ public class StartActivityDrawerTest { } @Test - public void testClickProviderName_closeDrawerAndShowEipFragment() { - preferencesEditor.putBoolean("navigation_drawer_learned", false).commit(); + public void testClickProviderName_closeDrawerAndShowEipFragment() throws InterruptedException { + preferencesEditor.putBoolean("navigation_drawer_learned", true).commit(); mActivityRule.launchActivity(intent); - + onView(withId(drawer_layout)).check(matches(isClosed())); + onView(withId(drawer_layout)).perform(DrawerActions.open()); + onView(withId(drawer_layout)).check(matches(isOpen())); onData(anything()).inAdapterView(withId(accountList)).atPosition(0).perform(click()); onView(withId(drawer_layout)).check(matches(isClosed())); onView(withId(eipServiceFragment)).check(matches(isDisplayed())); } @Test - public void testClickSwitchProvider_closeDrawerAndShowProviderListView() { - preferencesEditor.putBoolean("navigation_drawer_learned", false).commit(); + public void testSaveBattery_closeDrawerAndShowSaveBatteryDialog() throws InterruptedException { + preferencesEditor.putBoolean("navigation_drawer_learned", true).commit(); mActivityRule.launchActivity(intent); - + onView(withId(drawer_layout)).check(matches(isClosed())); + onView(withId(drawer_layout)).perform(DrawerActions.open()); + onView(withId(drawer_layout)).check(matches(isOpen())); onData(anything()).inAdapterView(withId(settingsList)).atPosition(0).perform(click()); + onView(withText(R.string.save_battery_message)) + .inRoot(isDialog()) + .check(matches(isDisplayed())); + } + + @Test + public void testAlwaysOnVPN_closeDrawerAndShowDialog() throws InterruptedException { + preferencesEditor.putBoolean("navigation_drawer_learned", true).commit(); + mActivityRule.launchActivity(intent); + onView(withId(drawer_layout)).check(matches(isClosed())); + onView(withId(drawer_layout)).perform(DrawerActions.open()); + onView(withId(drawer_layout)).check(matches(isOpen())); + onData(anything()).inAdapterView(withId(settingsList)).atPosition(1).perform(click()); + onView(withText(R.string.always_on_vpn_user_message)) + .inRoot(isDialog()) + .check(matches(isDisplayed())); + } + + @Test + public void testClickSwitchProvider_closeDrawerAndShowProviderListView() throws InterruptedException { + if (BuildConfig.FLAVOR_branding.equals("custom")) { + return; + } + preferencesEditor.putBoolean("navigation_drawer_learned", true).commit(); + mActivityRule.launchActivity(intent); + onView(withId(drawer_layout)).check(matches(isClosed())); + onView(withId(drawer_layout)).perform(DrawerActions.open()); + onView(withId(drawer_layout)).check(matches(isOpen())); + + onData(anything()).inAdapterView(withId(settingsList)).atPosition(3).perform(click()); onView(withId(provider_list_layout)).check(matches(isDisplayed())); } @Test - public void testClickLog_closeDrawerAndShowLogFragment() { - preferencesEditor.putBoolean("navigation_drawer_learned", false).commit(); + public void testClickLog_closeDrawerAndShowLogFragment() throws InterruptedException { + preferencesEditor.putBoolean("navigation_drawer_learned", true).commit(); mActivityRule.launchActivity(intent); + onView(withId(drawer_layout)).check(matches(isClosed())); + onView(withId(drawer_layout)).perform(DrawerActions.open()); + onView(withId(drawer_layout)).check(matches(isOpen())); - onData(anything()).inAdapterView(withId(settingsList)).atPosition(1).perform(click()); + onData(anything()).inAdapterView(withId(settingsList)).atPosition(getPositionBasedOnFlavor(2, 3)).perform(click()); onView(withId(log_layout)).check(matches(isDisplayed())); } @Test - public void testClickAbout_closeDrawerAndShowAboutFragment() { - preferencesEditor.putBoolean("navigation_drawer_learned", false).commit(); + public void testClickAbout_closeDrawerAndShowAboutFragment() throws InterruptedException { + preferencesEditor.putBoolean("navigation_drawer_learned", true).commit(); mActivityRule.launchActivity(intent); - - onData(anything()).inAdapterView(withId(settingsList)).atPosition(2).perform(click()); + onView(withId(drawer_layout)).check(matches(isClosed())); + onView(withId(drawer_layout)).perform(DrawerActions.open()); + onView(withId(drawer_layout)).check(matches(isOpen())); + onData(anything()).inAdapterView(withId(settingsList)).atPosition(getPositionBasedOnFlavor(4,5)).perform(click()); onView(withId(aboutLayout)).check(matches(isDisplayed())); } + + private int getPositionBasedOnFlavor(int custom, int defaultNumber) { + if (BuildConfig.FLAVOR_branding.equals("custom")) { + return custom; + } else { + return defaultNumber; + } + } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 66f36256..a6752cf7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,6 +24,7 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> + <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18"/> diff --git a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java index 5fbb440b..0fae6183 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -15,12 +15,10 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.LinkedList; import java.util.Locale; -import java.util.Queue; import java.util.Vector; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; public class VpnStatus { @@ -28,7 +26,7 @@ public class VpnStatus { private static final LinkedList<LogItem> logbuffer; private static Vector<LogListener> logListener; - private static Vector<StateListener> stateListener; + private static CopyOnWriteArrayList<StateListener> stateListener; private static Vector<ByteCountListener> byteCountListener; private static String mLaststatemsg = ""; @@ -200,7 +198,7 @@ public class VpnStatus { static { logbuffer = new LinkedList<>(); logListener = new Vector<>(); - stateListener = new Vector<>(); + stateListener = new CopyOnWriteArrayList<>(); byteCountListener = new Vector<>(); trafficHistory = new TrafficHistory(); @@ -214,6 +212,7 @@ public class VpnStatus { } public interface StateListener { + String STATE_CONNECTRETRY = "CONNECTRETRY"; void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level); void setConnectedVPN(String uuid); @@ -270,7 +269,7 @@ public class VpnStatus { if (!stateListener.contains(sl)) { stateListener.add(sl); if (mLaststate != null) - sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel); + sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel); } } @@ -383,7 +382,6 @@ public class VpnStatus { mLastStateresid = resid; mLastLevel = level; - for (StateListener sl : stateListener) { sl.updateState(state, msg, resid, level); } diff --git a/app/src/main/java/org/spongycastle/util/encoders/Encoder.java b/app/src/main/java/org/spongycastle/util/encoders/Encoder.java index 2007ac3e..a506c4f2 100644 --- a/app/src/main/java/org/spongycastle/util/encoders/Encoder.java +++ b/app/src/main/java/org/spongycastle/util/encoders/Encoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 Arne Schwabe + * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ diff --git a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java index 15fd85f8..fd9aa029 100644 --- a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java +++ b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java @@ -1,18 +1,25 @@ package se.leap.bitmaskclient; import android.content.Context; +import android.content.SharedPreferences; import android.support.multidex.MultiDexApplication; import com.squareup.leakcanary.LeakCanary; import com.squareup.leakcanary.RefWatcher; +import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; + /** * Created by cyberta on 24.10.17. */ public class BitmaskApp extends MultiDexApplication { + private final static String TAG = BitmaskApp.class.getSimpleName(); private RefWatcher refWatcher; + private ProviderObservable providerObservable; + @Override public void onCreate() { @@ -25,6 +32,10 @@ public class BitmaskApp extends MultiDexApplication { refWatcher = LeakCanary.install(this); // Normal app init code...*/ PRNGFixes.apply(); + SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + providerObservable = ProviderObservable.getInstance(); + providerObservable.updateProvider(getSavedProviderFromSharedPreferences(preferences)); + EipSetupObserver.init(this, preferences); } /** diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java index 15bec955..f9ee8fcf 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java @@ -33,6 +33,8 @@ public interface Constants { String DEFAULT_BITMASK = "normal"; String CUSTOM_BITMASK = "custom"; + String ASK_TO_CANCEL_VPN = "ask_to_cancel_vpn"; + ////////////////////////////////////////////// // EIP CONSTANTS @@ -51,6 +53,7 @@ public interface Constants { String EIP_RESTART_ON_BOOT = "EIP.RESTART_ON_BOOT"; String EIP_IS_ALWAYS_ON = "EIP.EIP_IS_ALWAYS_ON"; String EIP_EARLY_ROUTES = "EIP.EARLY_ROUTES"; + String EIP_N_CLOSEST_GATEWAY = "EIP.N_CLOSEST_GATEWAY"; ////////////////////////////////////////////// @@ -64,6 +67,7 @@ public interface Constants { String PROVIDER_KEY = "Constants.PROVIDER_KEY"; String PROVIDER_CONFIGURED = "Constants.PROVIDER_CONFIGURED"; String PROVIDER_EIP_DEFINITION = "Constants.EIP_DEFINITION"; + String PROVIDER_PROFILE_UUID = "Constants.PROVIDER_PROFILE_UUID"; ////////////////////////////////////////////// // CREDENTIAL CONSTANTS @@ -84,6 +88,7 @@ public interface Constants { String BROADCAST_EIP_EVENT = "BROADCAST.EIP_EVENT"; String BROADCAST_PROVIDER_API_EVENT = "BROADCAST.PROVIDER_API_EVENT"; + String BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT = "BROADCAST.GATEWAY_SETUP_WATCHER_EVENT"; String BROADCAST_RESULT_CODE = "BROADCAST.RESULT_CODE"; String BROADCAST_RESULT_KEY = "BROADCAST.RESULT_KEY"; diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java index fb4f16c7..a535b0cb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java @@ -19,7 +19,6 @@ package se.leap.bitmaskclient; import android.app.Activity; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; @@ -27,6 +26,7 @@ import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.os.Bundle; import android.os.IBinder; +import android.os.Vibrator; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; @@ -35,10 +35,13 @@ import android.support.v7.app.AlertDialog; import android.support.v7.widget.AppCompatImageView; import android.support.v7.widget.AppCompatTextView; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; import java.util.Observable; import java.util.Observer; @@ -58,23 +61,26 @@ import se.leap.bitmaskclient.views.VpnStateImage; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; -import static se.leap.bitmaskclient.Constants.DEFAULT_BITMASK; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_LOG_IN; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; +import static se.leap.bitmaskclient.Constants.ASK_TO_CANCEL_VPN; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.EipSetupObserver.connectionRetry; +import static se.leap.bitmaskclient.EipSetupObserver.gatewayOrder; +import static se.leap.bitmaskclient.EipSetupObserver.reconnectingWithDifferentGateway; import static se.leap.bitmaskclient.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE; import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; import static se.leap.bitmaskclient.utils.ConfigHelper.isDefaultBitmask; +import static se.leap.bitmaskclient.utils.ViewHelper.convertDimensionToPx; public class EipFragment extends Fragment implements Observer { public final static String TAG = EipFragment.class.getSimpleName(); - public static final String ASK_TO_CANCEL_VPN = "ask_to_cancel_vpn"; private SharedPreferences preferences; private Provider provider; @@ -94,11 +100,13 @@ public class EipFragment extends Fragment implements Observer { @InjectView(R.id.vpn_route) AppCompatTextView vpnRoute; + + private EipStatus eipStatus; //---saved Instance ------- - private final static String KEY_SHOW_PENDING_START_CANCELLATION = "KEY_SHOW_PENDING_START_CANCELLATION"; - private final static String KEY_SHOW_ASK_TO_STOP_EIP = "KEY_SHOW_ASK_TO_STOP_EIP"; + private final String KEY_SHOW_PENDING_START_CANCELLATION = "KEY_SHOW_PENDING_START_CANCELLATION"; + private final String KEY_SHOW_ASK_TO_STOP_EIP = "KEY_SHOW_ASK_TO_STOP_EIP"; private boolean showPendingStartCancellation = false; private boolean showAskToStopEip = false; //------------------------ @@ -193,7 +201,6 @@ public class EipFragment extends Fragment implements Observer { if (activity != null) { getActivity().unbindService(openVpnConnection); } - Log.d(TAG, "broadcast unregistered"); } @Override @@ -289,7 +296,7 @@ public class EipFragment extends Fragment implements Observer { Log.e(TAG, "context is null when trying to start VPN"); return; } - EipCommand.startVPN(context, false); + EipCommand.startVPN(context.getApplicationContext(), false); vpnStateImage.showProgress(); routedText.setVisibility(GONE); vpnRoute.setVisibility(GONE); @@ -302,7 +309,7 @@ public class EipFragment extends Fragment implements Observer { Log.e(TAG, "context is null when trying to stop EIP"); return; } - EipCommand.stopVPN(context); + EipCommand.stopVPN(context.getApplicationContext()); } private void askPendingStartCancellation() { @@ -316,22 +323,9 @@ public class EipFragment extends Fragment implements Observer { showPendingStartCancellation = true; alertDialog = alertBuilder.setTitle(activity.getString(R.string.eip_cancel_connect_title)) .setMessage(activity.getString(R.string.eip_cancel_connect_text)) - .setPositiveButton((android.R.string.yes), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - stopEipIfPossible(); - } - }) - .setNegativeButton(activity.getString(android.R.string.no), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - } - }).setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - showPendingStartCancellation = false; - } - }).show(); + .setPositiveButton((android.R.string.yes), (dialog, which) -> stopEipIfPossible()) + .setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> { + }).setOnDismissListener(dialog -> showPendingStartCancellation = false).show(); } @@ -345,22 +339,9 @@ public class EipFragment extends Fragment implements Observer { showAskToStopEip = true; alertDialog = alertBuilder.setTitle(activity.getString(R.string.eip_cancel_connect_title)) .setMessage(activity.getString(R.string.eip_warning_browser_inconsistency)) - .setPositiveButton((android.R.string.yes), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - stopEipIfPossible(); - } - }) - .setNegativeButton(activity.getString(android.R.string.no), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - } - }).setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - showAskToStopEip = false; - } - }).show(); + .setPositiveButton((android.R.string.yes), (dialog, which) -> stopEipIfPossible()) + .setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> { + }).setOnDismissListener(dialog -> showAskToStopEip = false).show(); } @Override @@ -369,15 +350,12 @@ public class EipFragment extends Fragment implements Observer { eipStatus = (EipStatus) observable; Activity activity = getActivity(); if (activity != null) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - handleNewState(); - } - }); + activity.runOnUiThread(() -> handleNewState()); } else { Log.e("EipFragment", "activity is null"); } + } else if (observable instanceof ProviderObservable) { + provider = ((ProviderObservable) observable).getCurrentProvider(); } } @@ -388,13 +366,16 @@ public class EipFragment extends Fragment implements Observer { return; } - if (eipStatus.isConnecting()) { - mainButton.setText(activity.getString(android.R.string.cancel)); - vpnStateImage.setStateIcon(R.drawable.vpn_connecting); - vpnStateImage.showProgress(); - routedText.setVisibility(GONE); - vpnRoute.setVisibility(GONE); - colorBackgroundALittle(); + Log.d(TAG, "eip fragment eipStatus state: " + eipStatus.getState() + " - level: " + eipStatus.getLevel() + " - is reconnecting: " + eipStatus.isReconnecting()); + + + if (eipStatus.isConnecting() ) { + showConnectingLayout(activity); + Log.d(TAG, "eip show connecting layout"); + if (eipStatus.isReconnecting()) { + Log.d(TAG, "eip show reconnecting toast!"); + showReconnectToast(activity); + } } else if (eipStatus.isConnected() ) { mainButton.setText(activity.getString(R.string.vpn_button_turn_off)); vpnStateImage.setStateIcon(R.drawable.vpn_connected); @@ -413,7 +394,11 @@ public class EipFragment extends Fragment implements Observer { vpnRoute.setVisibility(VISIBLE); setVpnRouteText(); colorBackgroundALittle(); - } else { + } else if (eipStatus.isDisconnected() && reconnectingWithDifferentGateway()) { + showConnectingLayout(activity); + showRetryToast(activity); + } + else { mainButton.setText(activity.getString(R.string.vpn_button_turn_on)); vpnStateImage.setStateIcon(R.drawable.vpn_disconnected); vpnStateImage.stopProgress(false); @@ -423,6 +408,48 @@ public class EipFragment extends Fragment implements Observer { } } + private void showToast(Activity activity, String message, boolean vibrateLong) { + LayoutInflater inflater = getLayoutInflater(); + View layout = inflater.inflate(R.layout.custom_toast, + (ViewGroup) activity.findViewById(R.id.custom_toast_container)); + + TextView text = (TextView) layout.findViewById(R.id.text); + text.setText(message); + + Vibrator v = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE); + if (vibrateLong) { + v.vibrate(100); + v.vibrate(200); + } else { + v.vibrate(100); + } + + Toast toast = new Toast(activity.getApplicationContext()); + toast.setGravity(Gravity.BOTTOM, 0, convertDimensionToPx(this.getContext(), R.dimen.stdpadding)); + toast.setDuration(Toast.LENGTH_LONG); + toast.setView(layout); + toast.show(); + } + private void showReconnectToast(Activity activity) { + String message = (String.format("Retry %d of %d before the next closest gateway will be selected.", connectionRetry()+1, 5)); + showToast(activity, message, false); + } + + private void showRetryToast(Activity activity) { + int nClosestGateway = gatewayOrder(); + String message = String.format("Server number " + nClosestGateway + " not reachable. Trying next gateway."); + showToast(activity, message, true ); + } + + private void showConnectingLayout(Context activity) { + mainButton.setText(activity.getString(android.R.string.cancel)); + vpnStateImage.setStateIcon(R.drawable.vpn_connecting); + vpnStateImage.showProgress(); + routedText.setVisibility(GONE); + vpnRoute.setVisibility(GONE); + colorBackgroundALittle(); + } + private boolean isOpenVpnRunningWithoutNetwork() { boolean isRunning = false; try { diff --git a/app/src/main/java/se/leap/bitmaskclient/EipSetupListener.java b/app/src/main/java/se/leap/bitmaskclient/EipSetupListener.java new file mode 100644 index 00000000..71e2fd52 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/EipSetupListener.java @@ -0,0 +1,12 @@ +package se.leap.bitmaskclient; + +import android.content.Intent; + +/** + * Created by cyberta on 05.12.18. + */ +public interface EipSetupListener { + void handleEipEvent(Intent intent); + + void handleProviderApiEvent(Intent intent); +} diff --git a/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java new file mode 100644 index 00000000..f17fe28e --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/EipSetupObserver.java @@ -0,0 +1,287 @@ +package se.leap.bitmaskclient; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; + +import java.util.Vector; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import de.blinkt.openvpn.LaunchVPN; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ConnectionStatus; +import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.eip.EipCommand; +import se.leap.bitmaskclient.eip.EipStatus; +import se.leap.bitmaskclient.eip.Gateway; +import se.leap.bitmaskclient.utils.PreferenceHelper; + +import static android.app.Activity.RESULT_CANCELED; +import static android.content.Intent.CATEGORY_DEFAULT; +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET; +import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT; +import static se.leap.bitmaskclient.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; +import static se.leap.bitmaskclient.Constants.BROADCAST_PROVIDER_API_EVENT; +import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; +import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; +import static se.leap.bitmaskclient.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; +import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP; +import static se.leap.bitmaskclient.Constants.EIP_REQUEST; +import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; +import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; +import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; + +/** + * Created by cyberta on 05.12.18. + */ +class EipSetupObserver extends BroadcastReceiver implements VpnStatus.StateListener { + + private static final String TAG = EipSetupObserver.class.getName(); + + //The real timout is 4*2s + 1*4s + 1*8s + 1*16s + 1*32s + 1*64s = 132 s; + private static final String TIMEOUT = "4"; + private Context context; + private VpnProfile setupVpnProfile; + private String observedProfileFromVpnStatus; + AtomicBoolean changingGateway = new AtomicBoolean(false); + AtomicInteger setupNClosestGateway = new AtomicInteger(); + AtomicInteger reconnectTry = new AtomicInteger(); + private Vector<EipSetupListener> listeners = new Vector<>(); + private SharedPreferences preferences; + private static EipSetupObserver instance; + + private EipSetupObserver(Context context, SharedPreferences preferences) { + this.context = context; + this.preferences = preferences; + IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); + updateIntentFilter.addAction(BROADCAST_EIP_EVENT); + updateIntentFilter.addAction(BROADCAST_PROVIDER_API_EVENT); + updateIntentFilter.addCategory(CATEGORY_DEFAULT); + LocalBroadcastManager.getInstance(context.getApplicationContext()).registerReceiver(this, updateIntentFilter); + instance = this; + } + + public static void init(Context context, SharedPreferences preferences) { + if (instance == null) { + instance = new EipSetupObserver(context, preferences); + } + } + + public static boolean reconnectingWithDifferentGateway() { + return instance.setupNClosestGateway.get() > 0; + } + + public static int connectionRetry() { + return instance.reconnectTry.get(); + } + + public static int gatewayOrder() { + return instance.setupNClosestGateway.get(); + } + + public static synchronized void addListener(EipSetupListener listener) { + if (instance.listeners.contains(listener)) { + return; + } + instance.listeners.add(listener); + } + + public static synchronized void removeListener(EipSetupListener listener) { + instance.listeners.remove(listener); + } + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "received Broadcast"); + + String action = intent.getAction(); + if (action == null) { + return; + } + + switch (action) { + case BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT: + handleGatewaySetupObserverEvent(intent); + break; + case BROADCAST_EIP_EVENT: + handleEipEvent(intent); + break; + case BROADCAST_PROVIDER_API_EVENT: + handleProviderApiEvent(intent); + break; + default: + break; + } + } + + private void handleProviderApiEvent(Intent intent) { + + + int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); + Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY); + if (resultData == null) { + resultData = Bundle.EMPTY; + } + + Provider provider; + switch (resultCode) { + case CORRECTLY_DOWNLOADED_EIP_SERVICE: + provider = resultData.getParcelable(PROVIDER_KEY); + ProviderObservable.getInstance().updateProvider(provider); + PreferenceHelper.storeProviderInPreferences(preferences, provider); + if (EipStatus.getInstance().isDisconnected()) { + EipCommand.startVPN(context.getApplicationContext(), true); + } + break; + case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: + provider = resultData.getParcelable(PROVIDER_KEY); + ProviderObservable.getInstance().updateProvider(provider); + EipCommand.startVPN(context.getApplicationContext(), true); + break; + default: + break; + } + + + + for (EipSetupListener listener : listeners) { + listener.handleProviderApiEvent(intent); + } + + } + + + private void handleEipEvent(Intent intent) { + int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); + String eipRequest = intent.getStringExtra(EIP_REQUEST); + if (eipRequest == null) { + return; + } + switch (eipRequest) { + case EIP_ACTION_START: + case EIP_ACTION_START_ALWAYS_ON_VPN: + if (resultCode == RESULT_CANCELED) { + //setup failed + finishGatewaySetup(false); + } + break; + case EIP_ACTION_STOP: + //setup was manually cancelled + finishGatewaySetup(false); + break; + default: + break; + } + + for (EipSetupListener listener : listeners) { + listener.handleEipEvent(intent); + } + + } + + private void handleGatewaySetupObserverEvent(Intent event) { + if (observedProfileFromVpnStatus != null || setupVpnProfile != null) { + //finish last setup observation + Log.d(TAG, "finish last gateway setup"); + finishGatewaySetup(true); + } + + VpnProfile vpnProfile = (VpnProfile) event.getSerializableExtra(LaunchVPN.EXTRA_TEMP_VPN_PROFILE); + if (vpnProfile == null) { + Log.e(TAG, "Tried to setup non existing vpn profile."); + return; + } + setupVpnProfile = vpnProfile; + setupNClosestGateway.set(event.getIntExtra(Gateway.KEY_N_CLOSEST_GATEWAY, 0)); + Log.d(TAG, "bitmaskapp add state listener"); + VpnStatus.addStateListener(this); + + launchVPN(setupVpnProfile); + } + + private void launchVPN(VpnProfile vpnProfile) { + Intent intent = new Intent(context.getApplicationContext(), LaunchVPN.class); + intent.setAction(Intent.ACTION_MAIN); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); + intent.putExtra(LaunchVPN.EXTRA_TEMP_VPN_PROFILE, vpnProfile); + intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, setupNClosestGateway.get()); + context.startActivity(intent); + } + + @Override + public void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level) { + // VpnStatus.updateStateString("NOPROCESS", "No process running.", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED); + + Log.d(TAG, "vpn status: " + state + " - " + logmessage + " - " + level); + if (observedProfileFromVpnStatus == null || + setupVpnProfile == null) { + return; + } + if (!observedProfileFromVpnStatus.equals(setupVpnProfile.getUUIDString())) { + Log.d(TAG, "vpn profile to setup and observed profile currently is used differ: " + setupVpnProfile.getUUIDString() + " vs. " + observedProfileFromVpnStatus); + return; + } + + Log.d(TAG, "trying gateway: " + setupVpnProfile.getName()); + + if ("CONNECTRETRY".equals(state) && LEVEL_CONNECTING_NO_SERVER_REPLY_YET.equals(level)) { + if (TIMEOUT.equals(logmessage)) { + Log.e(TAG, "Timeout reached! Try next gateway!"); + VpnStatus.logError("Timeout reached! Try next gateway!"); + selectNextGateway(); + return; + } + int current = reconnectTry.get(); + reconnectTry.set(current + 1); + } else if ("NOPROCESS".equals(state) && ConnectionStatus.LEVEL_NOTCONNECTED == level) { + //?? + } else if ("CONNECTED".equals(state)) { + //saveLastProfile(context.getApplicationContext(), setupVpnProfile.getUUIDString()); + if (setupNClosestGateway.get() > 0) { + //at least one failed gateway -> did the provider change it's gateways? + SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); + Provider provider = getSavedProviderFromSharedPreferences(preferences); + ProviderAPICommand.execute(context, ProviderAPI.DOWNLOAD_SERVICE_JSON, provider); + } + finishGatewaySetup(false); + } else if ("TCP_CONNECT".equals(state)) { + changingGateway.set(false); + } + } + + + private void selectNextGateway() { + changingGateway.set(true); + reconnectTry.set(0); + EipCommand.startVPN(context.getApplicationContext(), false, setupNClosestGateway.get() + 1); + } + + private void finishGatewaySetup(boolean changingGateway) { + VpnStatus.removeStateListener(this); + setupVpnProfile = null; + setupNClosestGateway.set(0); + observedProfileFromVpnStatus = null; + this.changingGateway.set(changingGateway); + this.reconnectTry.set(0); + } + + /** + * gets called as soon as a new VPN is about to launch + * + * @param uuid + */ + @Override + public void setConnectedVPN(String uuid) { + observedProfileFromVpnStatus = uuid; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java index 84c7c16a..fd0c27d6 100644 --- a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java @@ -17,16 +17,12 @@ package se.leap.bitmaskclient; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; -import android.support.v4.content.LocalBroadcastManager; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; @@ -35,45 +31,39 @@ import android.util.Log; import org.json.JSONException; import org.json.JSONObject; +import java.util.Observable; +import java.util.Observer; + import se.leap.bitmaskclient.drawer.NavigationDrawerFragment; import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.fragments.LogFragment; -import se.leap.bitmaskclient.utils.ConfigHelper; -import static android.content.Intent.CATEGORY_DEFAULT; -import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT; -import static se.leap.bitmaskclient.Constants.BROADCAST_PROVIDER_API_EVENT; +import static se.leap.bitmaskclient.Constants.ASK_TO_CANCEL_VPN; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; -import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP; import static se.leap.bitmaskclient.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_LOG_IN; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; -import static se.leap.bitmaskclient.EipFragment.ASK_TO_CANCEL_VPN; -import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE; -import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE; import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; -import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences; import static se.leap.bitmaskclient.utils.PreferenceHelper.storeProviderInPreferences; -public class MainActivity extends AppCompatActivity { +public class MainActivity extends AppCompatActivity implements EipSetupListener, Observer { public final static String TAG = MainActivity.class.getSimpleName(); - private Provider provider = new Provider(); + private Provider provider; private SharedPreferences preferences; private NavigationDrawerFragment navigationDrawerFragment; - private MainActivityBroadcastReceiver mainActivityBroadcastReceiver; public final static String ACTION_SHOW_VPN_FRAGMENT = "action_show_vpn_fragment"; public final static String ACTION_SHOW_LOG_FRAGMENT = "action_show_log_fragment"; @@ -88,14 +78,11 @@ public class MainActivity extends AppCompatActivity { setContentView(R.layout.a_main); setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - mainActivityBroadcastReceiver = new MainActivityBroadcastReceiver(); - setUpBroadcastReceiver(); - navigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_drawer); preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); - provider = getSavedProviderFromSharedPreferences(preferences); + provider = ProviderObservable.getInstance().getCurrentProvider(); // Set up the drawer. navigationDrawerFragment.setUp( @@ -108,6 +95,7 @@ public class MainActivity extends AppCompatActivity { @Override protected void onResume() { super.onResume(); + EipSetupObserver.addListener(this); } @Override @@ -183,17 +171,18 @@ public class MainActivity extends AppCompatActivity { } storeProviderInPreferences(preferences, provider); + ProviderObservable.getInstance().updateProvider(provider); navigationDrawerFragment.refresh(); switch (requestCode) { case REQUEST_CODE_SWITCH_PROVIDER: - EipCommand.stopVPN(this); + EipCommand.stopVPN(this.getApplicationContext()); break; case REQUEST_CODE_CONFIGURE_LEAP: Log.d(TAG, "REQUEST_CODE_CONFIGURE_LEAP - onActivityResult - MainActivity"); break; case REQUEST_CODE_LOG_IN: - EipCommand.startVPN(this, true); + EipCommand.startVPN(this.getApplicationContext(), true); break; } } @@ -210,51 +199,16 @@ public class MainActivity extends AppCompatActivity { @Override protected void onPause() { super.onPause(); + EipSetupObserver.removeListener(this); } @Override - protected void onDestroy() { - LocalBroadcastManager.getInstance(this).unregisterReceiver(mainActivityBroadcastReceiver); - mainActivityBroadcastReceiver = null; - super.onDestroy(); - } - - private void setUpBroadcastReceiver() { - IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_EIP_EVENT); - updateIntentFilter.addAction(BROADCAST_PROVIDER_API_EVENT); - updateIntentFilter.addCategory(CATEGORY_DEFAULT); - LocalBroadcastManager.getInstance(this).registerReceiver(mainActivityBroadcastReceiver, updateIntentFilter); - Log.d(TAG, "broadcast registered"); - } - - private class MainActivityBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - Log.d(TAG, "received Broadcast"); - - String action = intent.getAction(); - if (action == null) { - return; - } - - int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); - Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY); - if (resultData == null) { - resultData = Bundle.EMPTY; - } - - switch (action) { - case BROADCAST_EIP_EVENT: - handleEIPEvent(resultCode, resultData); - break; - case BROADCAST_PROVIDER_API_EVENT: - handleProviderApiEvent(resultCode, resultData); - break; - } + public void handleEipEvent(Intent intent) { + int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); + Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY); + if (resultData == null) { + resultData = Bundle.EMPTY; } - } - - private void handleEIPEvent(int resultCode, Bundle resultData) { String request = resultData.getString(EIP_REQUEST); if (request == null) { @@ -263,46 +217,26 @@ public class MainActivity extends AppCompatActivity { switch (request) { case EIP_ACTION_START: - switch (resultCode) { - case RESULT_OK: - break; - case RESULT_CANCELED: - String error = resultData.getString(ERRORS); - if (LeapSRPSession.loggedIn() || provider.allowsAnonymous()) { - showMainActivityErrorDialog(error); - } else { - askUserToLogIn(getString(vpn_certificate_user_message)); - } - break; - } - break; - case EIP_ACTION_STOP: - switch (resultCode) { - case RESULT_OK: - break; - case RESULT_CANCELED: - break; + if (resultCode == RESULT_CANCELED) { + String error = resultData.getString(ERRORS); + if (LeapSRPSession.loggedIn() || provider.allowsAnonymous()) { + showMainActivityErrorDialog(error); + } else { + askUserToLogIn(getString(vpn_certificate_user_message)); + } } break; } } - public void handleProviderApiEvent(int resultCode, Bundle resultData) { - // TODO call DOWNLOAD_EIP_SERVICES ore remove respective cases + @Override + public void handleProviderApiEvent(Intent intent) { + int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); + switch (resultCode) { - case CORRECTLY_DOWNLOADED_EIP_SERVICE: - provider = resultData.getParcelable(PROVIDER_KEY); - EipCommand.startVPN(this, true); - break; case INCORRECTLY_DOWNLOADED_EIP_SERVICE: // TODO CATCH ME IF YOU CAN - WHAT DO WE WANT TO DO? break; - - case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: - provider = resultData.getParcelable(PROVIDER_KEY); - storeProviderInPreferences(preferences, provider); - EipCommand.startVPN(this, true); - break; case INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: if (LeapSRPSession.loggedIn() || provider.allowsAnonymous()) { showMainActivityErrorDialog(getString(downloading_vpn_certificate_failed)); @@ -313,6 +247,13 @@ public class MainActivity extends AppCompatActivity { } } + @Override + public void update(Observable o, Object arg) { + if (o instanceof ProviderObservable) { + this.provider = ((ProviderObservable) o).getCurrentProvider(); + } + } + /** * Shows an error dialog */ diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java index 64a17f41..961957d2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java @@ -32,7 +32,6 @@ import android.support.v7.widget.AppCompatButton; import android.support.v7.widget.AppCompatTextView; import android.text.Editable; import android.text.Html; -import android.text.TextUtils; import android.text.TextWatcher; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; @@ -47,7 +46,6 @@ import org.json.JSONException; import butterknife.InjectView; import butterknife.OnClick; import se.leap.bitmaskclient.Constants.CREDENTIAL_ERRORS; -import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.userstatus.User; import static android.text.TextUtils.isEmpty; @@ -60,9 +58,7 @@ import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.Constants.CREDENTIALS_PASSWORD; import static se.leap.bitmaskclient.Constants.CREDENTIALS_USERNAME; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; -import static se.leap.bitmaskclient.ProviderAPI.BACKEND_ERROR_KEY; import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.ProviderAPI.LOG_IN; import static se.leap.bitmaskclient.ProviderAPI.SIGN_UP; import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE; @@ -442,6 +438,7 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc finish(); } + //TODO: replace with EipSetupObserver public class ProviderAPIBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java index a29d4b61..c8070e18 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java @@ -18,22 +18,12 @@ package se.leap.bitmaskclient; import android.content.Intent; -import android.content.IntentFilter; import android.os.Bundle; -import android.os.Handler; import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.content.LocalBroadcastManager; -import android.util.Log; import android.widget.ListView; import com.pedrogomez.renderers.Renderer; -import org.jetbrains.annotations.NotNull; -import org.json.JSONException; -import org.json.JSONObject; - import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; @@ -45,17 +35,9 @@ import butterknife.InjectView; import butterknife.OnItemClick; import se.leap.bitmaskclient.fragments.AboutFragment; -import static se.leap.bitmaskclient.Constants.BROADCAST_PROVIDER_API_EVENT; -import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_ADD_PROVIDER; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; -import static se.leap.bitmaskclient.ProviderAPI.ERRORS; -import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_SET_UP; -import static se.leap.bitmaskclient.ProviderAPI.UPDATE_PROVIDER_DETAILS; -import static se.leap.bitmaskclient.ProviderSetupInterface.ProviderConfigState.PENDING_SHOW_FAILED_DIALOG; -import static se.leap.bitmaskclient.ProviderSetupInterface.ProviderConfigState.PROVIDER_NOT_SET; import static se.leap.bitmaskclient.ProviderSetupInterface.ProviderConfigState.SETTING_UP_PROVIDER; -import static se.leap.bitmaskclient.ProviderSetupInterface.ProviderConfigState.SHOWING_PROVIDER_DETAILS; import static se.leap.bitmaskclient.ProviderSetupInterface.ProviderConfigState.SHOW_FAILED_DIALOG; /** diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderObservable.java b/app/src/main/java/se/leap/bitmaskclient/ProviderObservable.java new file mode 100644 index 00000000..776c0e92 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderObservable.java @@ -0,0 +1,31 @@ +package se.leap.bitmaskclient; + +import java.util.Observable; + +import se.leap.bitmaskclient.utils.PreferenceHelper; + +/** + * Created by cyberta on 05.12.18. + */ +public class ProviderObservable extends Observable { + private static ProviderObservable instance; + private Provider currentProvider; + + public static ProviderObservable getInstance() { + if (instance == null) { + instance = new ProviderObservable(); + } + return instance; + } + + public synchronized void updateProvider(Provider provider) { + instance.currentProvider = provider; + instance.setChanged(); + instance.notifyObservers(); + } + + public Provider getCurrentProvider() { + return instance.currentProvider; + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java index 9360bca3..4226d70f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java @@ -25,16 +25,17 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.util.Log; +import java.io.File; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.userstatus.User; -import se.leap.bitmaskclient.utils.ConfigHelper; +import se.leap.bitmaskclient.utils.PreferenceHelper; import static se.leap.bitmaskclient.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE; -import static se.leap.bitmaskclient.Constants.DEFAULT_BITMASK; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION; import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION; @@ -99,6 +100,9 @@ public class StartActivity extends Activity{ VpnStatus.initLogCache(getApplicationContext().getCacheDir()); User.init(getString(R.string.default_username)); + fakeSetup(); + + prepareEIP(); } @@ -184,7 +188,7 @@ public class StartActivity extends Activity{ } else { Log.d(TAG, "vpn provider is configured"); if (getIntent() != null && getIntent().getBooleanExtra(EIP_RESTART_ON_BOOT, false)) { - EipCommand.startVPN(this, true); + EipCommand.startVPN(this.getApplicationContext(), true); finish(); return; } @@ -213,7 +217,8 @@ public class StartActivity extends Activity{ if (resultCode == RESULT_OK && data != null && data.hasExtra(Provider.KEY)) { Provider provider = data.getParcelableExtra(Provider.KEY); storeProviderInPreferences(preferences, provider); - EipCommand.startVPN(this, false); + ProviderObservable.getInstance().updateProvider(provider); + EipCommand.startVPN(this.getApplicationContext(), false); showMainActivity(); } else if (resultCode == RESULT_CANCELED) { finish(); @@ -229,4 +234,145 @@ public class StartActivity extends Activity{ finish(); } + private void fakeSetup() { + PreferenceHelper.putString(this, "Constants.EIP_DEFINITION.riseup.net", getRiseupEipJson()); + PreferenceHelper.putString(this, "Constants.EIP_DEFINITION", getRiseupEipJson()); + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); + SharedPreferences.Editor prefsedit = prefs.edit(); + prefsedit.remove("lastConnectedProfile").commit(); + File f = new File(this.getCacheDir().getAbsolutePath() + "/android.conf"); + if (f.exists()) { + Log.d(TAG, "android.conf exists -> delete:" + f.delete()); + } + + File filesDirectory = new File(this.getFilesDir().getAbsolutePath()); + if (filesDirectory.exists() && filesDirectory.isDirectory()) { + File[] filesInDirectory = filesDirectory.listFiles(); + for (File file : filesInDirectory) { + Log.d(TAG, "delete profile: " + file.getName() + ": "+ file.delete()); + + } + } else Log.d(TAG, "file folder doesn't exist"); + + + Log.d(TAG, "faked eipjson: " + PreferenceHelper.getString(this, "Constants.EIP_DEFINITION", "")); + Log.d(TAG, "lastConnectedProfile is emty: " + (prefs.getString("lastConnectedProfile", null) == null)); + } + + private String getRiseupEipJson() { + return "{\n" + + " \"gateways\":[\n" + + " {\n" + + " \"capabilities\":{\n" + + " \"adblock\":false,\n" + + " \"filter_dns\":false,\n" + + " \"limited\":false,\n" + + " \"ports\":[\n" + + " \"443\"\n" + + " ],\n" + + " \"protocols\":[\n" + + " \"tcp\"\n" + + " ],\n" + + " \"transport\":[\n" + + " \"openvpn\"\n" + + " ],\n" + + " \"user_ips\":false\n" + + " },\n" + + " \"host\":\"garza.riseup.net\",\n" + + " \"ip_address\":\"198.252.153.28\",\n" + + " \"location\":\"seattle\"\n" + + " },\n" + + " {\n" + + " \"capabilities\":{\n" + + " \"adblock\":false,\n" + + " \"filter_dns\":false,\n" + + " \"limited\":false,\n" + + " \"ports\":[\n" + + " \"443\"\n" + + " ],\n" + + " \"protocols\":[\n" + + " \"tcp\"\n" + + " ],\n" + + " \"transport\":[\n" + + " \"openvpn\"\n" + + " ],\n" + + " \"user_ips\":false\n" + + " },\n" + + " \"host\":\"no.giraffe.riseup.net\",\n" + + " \"ip_address\":\"37.218.242.212\",\n" + + " \"location\":\"amsterdam\"\n" + + " },\n" + + " {\n" + + " \"capabilities\":{\n" + + " \"adblock\":false,\n" + + " \"filter_dns\":false,\n" + + " \"limited\":false,\n" + + " \"ports\":[\n" + + " \"443\"\n" + + " ],\n" + + " \"protocols\":[\n" + + " \"tcp\"\n" + + " ],\n" + + " \"transport\":[\n" + + " \"openvpn\"\n" + + " ],\n" + + " \"user_ips\":false\n" + + " },\n" + + " \"host\":\"no.tenca.riseup.net\",\n" + + " \"ip_address\":\"5.79.86.181\",\n" + + " \"location\":\"amsterdam\"\n" + + " },\n" + + " {\n" + + " \"capabilities\":{\n" + + " \"adblock\":false,\n" + + " \"filter_dns\":false,\n" + + " \"limited\":false,\n" + + " \"ports\":[\n" + + " \"443\"\n" + + " ],\n" + + " \"protocols\":[\n" + + " \"tcp\"\n" + + " ],\n" + + " \"transport\":[\n" + + " \"openvpn\"\n" + + " ],\n" + + " \"user_ips\":false\n" + + " },\n" + + " \"host\":\"yal.riseup.net\",\n" + + " \"ip_address\":\"199.58.81.145\",\n" + + " \"location\":\"montreal\"\n" + + " }\n" + + " ],\n" + + " \"locations\":{\n" + + " \"amsterdam\":{\n" + + " \"country_code\":\"NL\",\n" + + " \"hemisphere\":\"N\",\n" + + " \"name\":\"Amsterdam\",\n" + + " \"timezone\":\"+2\"\n" + + " },\n" + + " \"montreal\":{\n" + + " \"country_code\":\"CA\",\n" + + " \"hemisphere\":\"N\",\n" + + " \"name\":\"Montreal\",\n" + + " \"timezone\":\"-5\"\n" + + " },\n" + + " \"seattle\":{\n" + + " \"country_code\":\"US\",\n" + + " \"hemisphere\":\"N\",\n" + + " \"name\":\"Seattle\",\n" + + " \"timezone\":\"-7\"\n" + + " }\n" + + " },\n" + + " \"openvpn_configuration\":{\n" + + " \"auth\":\"SHA1\",\n" + + " \"cipher\":\"AES-128-CBC\",\n" + + " \"keepalive\":\"10 30\",\n" + + " \"tls-cipher\":\"DHE-RSA-AES128-SHA\",\n" + + " \"tun-ipv6\":true\n" + + " },\n" + + " \"serial\":1,\n" + + " \"version\":1\n" + + "}"; + } + } diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java b/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java index 503ba536..566b3453 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnNotificationManager.java @@ -44,7 +44,7 @@ import static android.text.TextUtils.isEmpty; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN; -import static se.leap.bitmaskclient.EipFragment.ASK_TO_CANCEL_VPN; +import static se.leap.bitmaskclient.Constants.ASK_TO_CANCEL_VPN; import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT; /** diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java index 769bd887..a01a79ea 100644 --- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -47,7 +47,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.CompoundButton; import android.widget.ListView; import se.leap.bitmaskclient.DrawerSettingsAdapter; @@ -201,12 +200,7 @@ public class NavigationDrawerFragment extends Fragment { } // Defer code dependent on restoration of previous instance state. - this.drawerLayout.post(new Runnable() { - @Override - public void run() { - drawerToggle.syncState(); - } - }); + this.drawerLayout.post(() -> drawerToggle.syncState()); this.drawerLayout.addDrawerListener(drawerToggle); } @@ -261,12 +255,7 @@ public class NavigationDrawerFragment extends Fragment { private void setupSettingsListView() { ListView drawerSettingsListView = drawerView.findViewById(R.id.settingsList); - drawerSettingsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - selectItem(parent, position); - } - }); + drawerSettingsListView.setOnItemClickListener((parent, view, position, id) -> selectItem(parent, position)); drawerSettingsListView.setAdapter(settingsListAdapter); } @@ -276,12 +265,7 @@ public class NavigationDrawerFragment extends Fragment { settingsListAdapter.addItem(getSwitchInstance(getString(R.string.save_battery), getSaveBattery(getContext()), BATTERY_SAVER, - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean newStateIsChecked) { - onSwitchItemSelected(BATTERY_SAVER, newStateIsChecked); - } - })); + (buttonView, newStateIsChecked) -> onSwitchItemSelected(BATTERY_SAVER, newStateIsChecked))); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { settingsListAdapter.addItem(getSimpleTextInstance(getString(R.string.always_on_vpn), ALWAYS_ON)); @@ -318,31 +302,25 @@ public class NavigationDrawerFragment extends Fragment { private void showDottedIconWithDelay() { final Handler navigationDrawerHandler = new Handler(); - navigationDrawerHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (!wasPaused) { - toolbar.setNavigationIcon(R.drawable.ic_menu_color_point); - toolbar.playSoundEffect(android.view.SoundEffectConstants.CLICK); - } - + navigationDrawerHandler.postDelayed(() -> { + if (!wasPaused) { + toolbar.setNavigationIcon(R.drawable.ic_menu_color_point); + toolbar.playSoundEffect(android.view.SoundEffectConstants.CLICK); } + }, THREE_SECONDS); } @NonNull private void closeDrawerWithDelay() { final Handler navigationDrawerHandler = new Handler(); - navigationDrawerHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (!wasPaused) { - drawerLayout.closeDrawer(fragmentContainerView, true); - } else { - shouldCloseOnResume = true; - } - + navigationDrawerHandler.postDelayed(() -> { + if (!wasPaused) { + drawerLayout.closeDrawer(fragmentContainerView, true); + } else { + shouldCloseOnResume = true; } + }, TWO_SECONDS); } @@ -382,31 +360,18 @@ public class NavigationDrawerFragment extends Fragment { alertDialog = alertBuilder .setTitle(activity.getString(R.string.save_battery)) .setMessage(activity.getString(R.string.save_battery_message)) - .setPositiveButton((android.R.string.yes), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - DrawerSettingsItem item = settingsListAdapter.getDrawerItem(BATTERY_SAVER); - item.setChecked(true); - settingsListAdapter.notifyDataSetChanged(); - saveBattery(getContext(), item.isChecked()); - } + .setPositiveButton((android.R.string.yes), (dialog, which) -> { + DrawerSettingsItem item = settingsListAdapter.getDrawerItem(BATTERY_SAVER); + item.setChecked(true); + settingsListAdapter.notifyDataSetChanged(); + saveBattery(getContext(), item.isChecked()); }) - .setNegativeButton(activity.getString(android.R.string.no), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - disableSwitch(BATTERY_SAVER); - } - }).setOnDismissListener(new DialogInterface.OnDismissListener() { + .setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> disableSwitch(BATTERY_SAVER)).setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { showEnableExperimentalFeature = false; } - }).setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - disableSwitch(BATTERY_SAVER); - } - }).show(); + }).setOnCancelListener(dialog -> disableSwitch(BATTERY_SAVER)).show(); } catch (IllegalStateException e) { e.printStackTrace(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index 971d973f..0e6d9b95 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -53,7 +53,9 @@ import se.leap.bitmaskclient.OnBootReceiver; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; import static android.content.Intent.CATEGORY_DEFAULT; +import static de.blinkt.openvpn.LaunchVPN.EXTRA_TEMP_VPN_PROFILE; import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT; +import static se.leap.bitmaskclient.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; @@ -63,6 +65,7 @@ import static se.leap.bitmaskclient.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP; import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN; import static se.leap.bitmaskclient.Constants.EIP_EARLY_ROUTES; +import static se.leap.bitmaskclient.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.Constants.EIP_RECEIVER; import static se.leap.bitmaskclient.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; @@ -150,10 +153,12 @@ public final class EIP extends JobIntentService implements Observer { if (intent.getParcelableExtra(EIP_RECEIVER) != null) { mResultRef = new WeakReference<>((ResultReceiver) intent.getParcelableExtra(EIP_RECEIVER)); } + int nClosestGateway; switch (action) { case EIP_ACTION_START: boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, true); - startEIP(earlyRoutes); + nClosestGateway = intent.getIntExtra(EIP_N_CLOSEST_GATEWAY, 0); + startEIP(earlyRoutes, nClosestGateway); break; case EIP_ACTION_START_ALWAYS_ON_VPN: startEIPAlwaysOnVpn(); @@ -170,13 +175,17 @@ public final class EIP extends JobIntentService implements Observer { } } + /** * Initiates an EIP connection by selecting a gateway and preparing and sending an * Intent to {@link de.blinkt.openvpn.LaunchVPN}. * It also sets up early routes. + * @param earlyRoutes if true, a void vpn gets set up + * @param nClosestGateway the gateway that is the n nearest one to the users place */ @SuppressLint("ApplySharedPref") - private void startEIP(boolean earlyRoutes) { + private void startEIP(boolean earlyRoutes, int nClosestGateway) { + Log.d(TAG, "start EIP with early routes: " + earlyRoutes + " and nClosest Gateway: " + nClosestGateway); if (!eipStatus.isBlockingVpnEstablished() && earlyRoutes) { earlyRoutes(); } @@ -186,16 +195,17 @@ public final class EIP extends JobIntentService implements Observer { preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, true).commit(); } - GatewaysManager gatewaysManager = gatewaysFromPreferences(); if (!isVPNCertificateValid()) { setErrorResult(result, vpn_certificate_is_invalid, ERROR_INVALID_VPN_CERTIFICATE.toString()); - tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_CANCELED); + tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_CANCELED, result); return; } - Gateway gateway = gatewaysManager.select(); + GatewaysManager gatewaysManager = gatewaysFromPreferences(); + Gateway gateway = gatewaysManager.select(nClosestGateway); + if (gateway != null && gateway.getProfile() != null) { - launchActiveGateway(gateway); + launchActiveGateway(gateway, nClosestGateway); tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_OK); } else tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_CANCELED); @@ -209,11 +219,11 @@ public final class EIP extends JobIntentService implements Observer { Log.d(TAG, "startEIPAlwaysOnVpn vpn"); GatewaysManager gatewaysManager = gatewaysFromPreferences(); - Gateway gateway = gatewaysManager.select(); + Gateway gateway = gatewaysManager.select(0); if (gateway != null && gateway.getProfile() != null) { - Log.d(TAG, "startEIPAlwaysOnVpn eip launch avtive gateway vpn"); - launchActiveGateway(gateway); + Log.d(TAG, "startEIPAlwaysOnVpn eip launch closest gateway."); + launchActiveGateway(gateway, 0); } else { Log.d(TAG, "startEIPAlwaysOnVpn no active profile available!"); } @@ -234,13 +244,21 @@ public final class EIP extends JobIntentService implements Observer { * * @param gateway to connect to */ - private void launchActiveGateway(Gateway gateway) { - Intent intent = new Intent(this, LaunchVPN.class); - intent.setAction(Intent.ACTION_MAIN); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); - intent.putExtra(LaunchVPN.EXTRA_TEMP_VPN_PROFILE, gateway.getProfile()); - startActivity(intent); + private void launchActiveGateway(@NonNull Gateway gateway, int nClosestGateway) { + /*Intent gatewaySetupWatcherIntent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); + gatewaySetupWatcherIntent.putExtra(EIP_REQUEST, ) + gatewaySetupWatcherIntent.putExtra(LaunchVPN.EXTRA_TEMP_VPN_PROFILE, gateway.getProfile());*/ + + + Intent intent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); //new Intent(this, LaunchVPN.class); + //intent.setAction(Intent.ACTION_MAIN); + //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); + intent.putExtra(EXTRA_TEMP_VPN_PROFILE, gateway.getProfile()); + intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, nClosestGateway); + //startActivity(intent); + LocalBroadcastManager.getInstance(this).sendBroadcast(intent); + } /** @@ -277,6 +295,14 @@ public final class EIP extends JobIntentService implements Observer { } /** + * Updates the eip.json. It containes information about the vpn service of a provider such as + * available gateways, supported protocols and open ports. + */ + private void updateEipJson() { + + } + + /** * read VPN certificate from preferences and check it * broadcast result */ diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java index 19735483..2bca5c9b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java @@ -10,10 +10,13 @@ import android.support.annotation.VisibleForTesting; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import se.leap.bitmaskclient.Provider; + import static se.leap.bitmaskclient.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; import static se.leap.bitmaskclient.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP; import static se.leap.bitmaskclient.Constants.EIP_EARLY_ROUTES; +import static se.leap.bitmaskclient.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.Constants.EIP_RECEIVER; /** @@ -22,7 +25,7 @@ import static se.leap.bitmaskclient.Constants.EIP_RECEIVER; public class EipCommand { - public static void execute(@NotNull Context context, @NotNull String action) { + private static void execute(@NotNull Context context, @NotNull String action) { execute(context, action, null, null); } @@ -33,7 +36,7 @@ public class EipCommand { * filter for the EIP class * @param resultReceiver The resultreceiver to reply to */ - public static void execute(@NotNull Context context, @NotNull String action, @Nullable ResultReceiver resultReceiver, @Nullable Intent vpnIntent) { + private static void execute(@NotNull Context context, @NotNull String action, @Nullable ResultReceiver resultReceiver, @Nullable Intent vpnIntent) { // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? if (vpnIntent == null) { vpnIntent = new Intent(); @@ -48,6 +51,14 @@ public class EipCommand { public static void startVPN(@NonNull Context context, boolean earlyRoutes) { Intent baseIntent = new Intent(); baseIntent.putExtra(EIP_EARLY_ROUTES, earlyRoutes); + baseIntent.putExtra(EIP_N_CLOSEST_GATEWAY, 0); + execute(context, EIP_ACTION_START, null, baseIntent); + } + + public static void startVPN(@NonNull Context context, boolean earlyRoutes, int nClosestGateway) { + Intent baseIntent = new Intent(); + baseIntent.putExtra(EIP_EARLY_ROUTES, earlyRoutes); + baseIntent.putExtra(EIP_N_CLOSEST_GATEWAY, nClosestGateway); execute(context, EIP_ACTION_START, null, baseIntent); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java index df252500..861f5fd3 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -19,6 +19,7 @@ package se.leap.bitmaskclient.eip; import android.content.Context; import android.os.AsyncTask; import android.support.annotation.VisibleForTesting; +import android.util.Log; import java.util.Observable; @@ -26,6 +27,7 @@ import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.LogItem; import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.Provider; /** * EipStatus is a Singleton that represents a reduced set of a vpn's ConnectionStatus. @@ -76,7 +78,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { currentStatus.setLocalizedResId(localizedResId); currentStatus.setLevel(level); currentStatus.setEipLevel(level); - if (tmp != currentStatus.getLevel()) { + if (tmp != currentStatus.getLevel() || "RECONNECTING".equals(state)) { currentStatus.setChanged(); currentStatus.notifyObservers(); } @@ -86,6 +88,10 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { public void setConnectedVPN(String uuid) { } + public boolean isReconnecting() { + Log.d(TAG, "eip currentVPNStatus : " + currentStatus.getState() ); + return "RECONNECTING".equals(currentStatus.getState()); + } private void setEipLevel(ConnectionStatus level) { switch (level) { diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 6cccdcd2..317a91bd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -38,6 +38,7 @@ import de.blinkt.openvpn.core.ConfigParser; public class Gateway { public final static String TAG = Gateway.class.getSimpleName(); + public final static String KEY_N_CLOSEST_GATEWAY = "N_CLOSEST_GATEWAY"; private JSONObject generalConfiguration; private JSONObject secrets; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java index 90c8f890..2bd666bf 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java @@ -1,26 +1,52 @@ package se.leap.bitmaskclient.eip; -import java.util.*; +import android.util.Log; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import static se.leap.bitmaskclient.utils.ConfigHelper.getCurrentTimezone; public class GatewaySelector { + private final static String TAG = GatewaySelector.class.getSimpleName(); List<Gateway> gateways; + TreeMap<Integer, Set<Gateway>> offsets; public GatewaySelector(List<Gateway> gateways) { this.gateways = gateways; + this.offsets = calculateOffsets(); + } public Gateway select() { return closestGateway(); } + public Gateway select(int nClosest) { + int i = 0; + for (Map.Entry<Integer,Set<Gateway>> entrySet : offsets.entrySet()) { + for (Gateway gateway : entrySet.getValue()) { + if (i == nClosest) { + return gateway; + } + i = i + 1; + } + } + + Log.e(TAG, "There are less than " + nClosest + " Gateways available."); + return null; + } + private Gateway closestGateway() { - TreeMap<Integer, Set<Gateway>> offsets = calculateOffsets(); return offsets.isEmpty() ? null : offsets.firstEntry().getValue().iterator().next(); } private TreeMap<Integer, Set<Gateway>> calculateOffsets() { TreeMap<Integer, Set<Gateway>> offsets = new TreeMap<Integer, Set<Gateway>>(); - int localOffset = Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 3600000; + int localOffset = getCurrentTimezone(); for (Gateway gateway : gateways) { int dist = timezoneDistance(localOffset, gateway.getTimezone()); Set<Gateway> set = (offsets.get(dist) != null) ? diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index 5b4db5af..003cef7d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -18,6 +18,7 @@ package se.leap.bitmaskclient.eip; import android.content.Context; import android.content.SharedPreferences; +import android.util.Log; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -46,6 +47,8 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; */ public class GatewaysManager { + private static final String TAG = GatewaysManager.class.getSimpleName(); + private Context context; private SharedPreferences preferences; private List<Gateway> gateways = new ArrayList<>(); @@ -60,11 +63,11 @@ public class GatewaysManager { /** * select closest Gateway - * @return the closest Gateway + * @return the n closest Gateway */ - public Gateway select() { + public Gateway select(int nClosest) { GatewaySelector gatewaySelector = new GatewaySelector(gateways); - return gatewaySelector.select(); + return gatewaySelector.select(nClosest); } /** diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java index 326139c0..d1ac0eb3 100644 --- a/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/ConfigHelper.java @@ -39,6 +39,7 @@ import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Calendar; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.ProviderAPI; @@ -177,4 +178,8 @@ public class ConfigHelper { public static boolean preferAnonymousUsage() { return BuildConfig.priotize_anonymous_usage; } + + public static int getCurrentTimezone() { + return Calendar.getInstance().get(Calendar.ZONE_OFFSET) / 3600000; + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java index b1da1284..44831049 100644 --- a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java @@ -2,7 +2,6 @@ package se.leap.bitmaskclient.utils; import android.content.Context; import android.content.SharedPreferences; -import android.os.Looper; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -10,12 +9,6 @@ import android.support.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; -import java.io.ByteArrayInputStream; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; @@ -239,6 +232,29 @@ public class PreferenceHelper { return result; } + /*public static void saveLastProfile(Context context, String uuid) { + if (context == null) { + return; + } + putString(context, PROVIDER_PROFILE_UUID, uuid); + } + + public static void clearLastProfile(Context context) { + if (context == null) { + return; + } + SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); + preferences.edit().remove(PROVIDER_PROFILE_UUID).apply(); + } + + public static String getLastProfile(Context context){ + return getString(context, PROVIDER_PROFILE_UUID, null); + } + + public static void saveLastGatewayNumber(Context context, int number) { + + } +*/ public static String getString(Context context, String key, String defValue) { SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); return preferences.getString(key, defValue); diff --git a/app/src/main/res/drawable/cust_toast_background.xml b/app/src/main/res/drawable/cust_toast_background.xml new file mode 100644 index 00000000..a27c96f8 --- /dev/null +++ b/app/src/main/res/drawable/cust_toast_background.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> + +<layer-list> + <item> + <shape android:shape="rectangle" + xmlns:android="http://schemas.android.com/apk/res/android"> + <corners android:radius="50dp" /> + <solid android:color="@android:color/white"/> + </shape> + </item> + <item> + <shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android"> + <corners android:radius="50dp" /> + <padding android:left="8dp" android:right="8dp"/> + <solid android:color="@android:color/transparent"/> + <stroke android:width="2dp" android:color="@color/colorPrimary"/> + </shape> + </item> +</layer-list> diff --git a/app/src/main/res/drawable/retry.png b/app/src/main/res/drawable/retry.png Binary files differnew file mode 100755 index 00000000..ce722398 --- /dev/null +++ b/app/src/main/res/drawable/retry.png diff --git a/app/src/main/res/layout/custom_toast.xml b/app/src/main/res/layout/custom_toast.xml new file mode 100644 index 00000000..70a9df8c --- /dev/null +++ b/app/src/main/res/layout/custom_toast.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/custom_toast_container" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:padding="16dp" + android:background="@drawable/cust_toast_background"> + <ImageView android:src="@drawable/retry" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginRight="8dp" + android:layout_gravity="center" + /> + <TextView android:id="@+id/text" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#000000" + android:layout_gravity="center" + tools:text="Could not connect to gateway XYZ (161.161.161.161). Trying different Gateway." + /> +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 00000000..eb9626bc --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <declare-styleable name="foo"> + <attr name="textColorError" format="color" /> + </declare-styleable> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 10c32471..338ad4d5 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -39,4 +39,6 @@ <dimen name="constraint_bottom_std">0.85</dimen> <dimen name="constraint_top_compact">0.1</dimen> <dimen name="constraint_bottom_compact">0.9</dimen> + + <dimen name="toast_bottom_padding">20dp</dimen> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 70bc3e16..f3c55f41 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -21,4 +21,8 @@ <item name="android:windowBackground">@drawable/splash_page</item> </style> + <style name="foo"> + <item name="textColorError">@color/colorPrimary</item> + </style> + </resources> diff --git a/app/src/main/res/values/untranslatable.xml b/app/src/main/res/values/untranslatable.xml index 7270acf6..07cca36e 100644 --- a/app/src/main/res/values/untranslatable.xml +++ b/app/src/main/res/values/untranslatable.xml @@ -36,4118 +36,5 @@ <string name="state_user_vpn_password" translatable="false">Waiting for user VPN password</string> <string name="state_user_vpn_password_cancelled" translatable="false">VPN password input dialog cancelled</string> <string name="state_user_vpn_permission_cancelled" translatable="false">VPN API permission dialog cancelled</string> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <string name="crash_toast_text">OpenVPN for Android crashed, crash reported</string> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + <string name="gateway_not_reachable">Could not connect to gateway %s. Trying next one.</string> </resources>
\ No newline at end of file diff --git a/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java index b1afa6d3..fe7e2491 100644 --- a/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java +++ b/app/src/production/java/se/leap/bitmaskclient/ProviderApiManager.java @@ -158,7 +158,6 @@ public class ProviderApiManager extends ProviderApiManagerBase { String eipServiceUrl = providerJson.getString(Provider.API_URL) + "/" + providerJson.getString(Provider.API_VERSION) + "/" + EIP.SERVICE_API_PATH; eipServiceJsonString = downloadWithProviderCA(provider.getCaCert(), eipServiceUrl); JSONObject eipServiceJson = new JSONObject(eipServiceJsonString); - eipServiceJson.getInt(Provider.API_RETURN_SERIAL); provider.setEipServiceJson(eipServiceJson); diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaySelectorTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaySelectorTest.java new file mode 100644 index 00000000..6d858d39 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaySelectorTest.java @@ -0,0 +1,172 @@ +package se.leap.bitmaskclient.eip; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +import java.io.IOException; +import java.util.ArrayList; + +import se.leap.bitmaskclient.Provider; +import se.leap.bitmaskclient.utils.ConfigHelper; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; +import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; +import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; + +/** + * Created by cyberta on 18.12.18. + */ +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(DataProviderRunner.class) +@PrepareForTest({ConfigHelper.class}) +public class GatewaySelectorTest { + public static final String TAG = GatewaySelectorTest.class.getSimpleName(); + + /** + * locations": { + "name": ""Frankfurt"", + "timezone": "+1" + }, + ""Seattle, WA"__wa": { + "name": ""Seattle, WA", WA", + "timezone": "-7" + }, + ""Moscow"": { + "country_code": "RU", + "hemisphere": "N", + "name": ""Moscow"", + "timezone": "+3" + }, + ""Manila"": { + "country_code": "PH", + "hemisphere": "N", + "name": ""Manila"", + "timezone": "+8" + } + }, + */ + + + GatewaySelector gatewaySelector; + JSONObject eipDefinition; + ArrayList<Gateway> gatewayList = new ArrayList<>(); + + @Before + public void setup() throws IOException, JSONException { + mockStatic(ConfigHelper.class); + eipDefinition = new JSONObject(getInputAsString(getClass().getClassLoader().getResourceAsStream("eip-service-four-gateways.json"))); + JSONArray gateways = eipDefinition.getJSONArray("gateways"); + for (int i = 0; i < gateways.length(); i++) { + JSONObject gw = gateways.getJSONObject(i); + JSONObject secrets = secretsConfiguration(); + Gateway aux = new Gateway(eipDefinition, secrets, gw); + gatewayList.add(aux); + } + + } + + private JSONObject secretsConfiguration() throws IOException, JSONException { + JSONObject result = new JSONObject(); + result.put(Provider.CA_CERT, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"))); + result.put(PROVIDER_PRIVATE_KEY, getInputAsString(getClass().getClassLoader().getResourceAsStream("private_rsa_key.pem"))); + result.put(PROVIDER_VPN_CERTIFICATE, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.vpn_cert.pem"))); + return result; + } + + @DataProvider + public static Object[][] dataProviderTimezones() { + // @formatter:off + return new Object[][] { + //{ -12, "Seattle, WA" } + { -11, "Seattle, WA" }, + { -10, "Seattle, WA" }, + { -9, "Seattle, WA" }, + { -8, "Seattle, WA" }, + { -7, "Seattle, WA" }, // <-"Seattle, WA" + { -6, "Seattle, WA" }, + { -5, "Seattle, WA" }, + { -4, "Seattle, WA" }, + // { -3, "Seattle, WA" }, + { -2, "Frankfurt" }, + { -1, "Frankfurt" }, + { 0, "Frankfurt" }, + { 1, "Frankfurt" }, // <- "Frankfurt" + // { 2, "Moscow" }, + { 3, "Moscow" }, // <- "Moscow" + { 4, "Moscow" }, + { 5, "Moscow" }, + { 6, "Manila" }, + { 7, "Manila" }, + { 8, "Manila" }, // <- "Manila" + { 9, "Manila" }, + { 10, "Manila" }, + { 11, "Manila" }, + { 12, "Manila" } + }; + // @formatter:on + } + + @DataProvider + public static Object[][] dataProviderSameDistanceTimezones() { + // @formatter:off + return new Object[][] { + { -12, "Seattle, WA", "Manila" }, + { -3, "Seattle, WA", "Frankfurt" }, + { 2, "Moscow", "Frankfurt" }, + + }; + // @formatter:on + } + + @Test + @UseDataProvider("dataProviderTimezones") + public void testSelect(int timezone, String expected) { + when(ConfigHelper.getCurrentTimezone()).thenReturn(timezone); + gatewaySelector = new GatewaySelector(gatewayList); + assertEquals(expected, gatewaySelector.select().getName()); + } + + @Test + @UseDataProvider("dataProviderSameDistanceTimezones") + public void testSelectSameTimezoneDistance(int timezone, String expected1, String expected2) { + when(ConfigHelper.getCurrentTimezone()).thenReturn(timezone); + gatewaySelector = new GatewaySelector(gatewayList); + assertTrue(gatewaySelector.select().getName().equals(expected1) || gatewaySelector.select().getName().equals(expected2)); + } + + @Test + @UseDataProvider("dataProviderSameDistanceTimezones") + public void testNClostest_SameTimezoneDistance_chooseGatewayWithSameDistance(int timezone, String expected1, String expected2) { + when(ConfigHelper.getCurrentTimezone()).thenReturn(timezone); + gatewaySelector = new GatewaySelector(gatewayList); + ArrayList<String> gateways = new ArrayList<>(); + gateways.add(gatewaySelector.select(0).getName()); + gateways.add(gatewaySelector.select(1).getName()); + + assertTrue(gateways.contains(expected1) && gateways.contains(expected2)); + + } + + @Test + public void testNClostest_OneTimezonePerSet_choseSecondClosestTimezone() { + when(ConfigHelper.getCurrentTimezone()).thenReturn(-4); + gatewaySelector = new GatewaySelector(gatewayList); + + assertTrue("Frankfurt".equals(gatewaySelector.select(1).getName())); + } +}
\ No newline at end of file diff --git a/app/src/test/resources/eip-service-four-gateways.json b/app/src/test/resources/eip-service-four-gateways.json new file mode 100644 index 00000000..a3395b72 --- /dev/null +++ b/app/src/test/resources/eip-service-four-gateways.json @@ -0,0 +1,123 @@ +{ + "gateways": [ + { + "capabilities": { + "adblock": false, + "filter_dns": false, + "limited": false, + "ports": [ + "443" + ], + "protocols": [ + "tcp", + "udp" + ], + "transport": [ + "openvpn" + ], + "user_ips": false + }, + "host": "millipede.demo.bitmask.net", + "ip_address": "198.252.153.84", + "location": "seattle__wa" + }, + { + "capabilities": { + "adblock": false, + "filter_dns": false, + "limited": false, + "ports": [ + "443" + ], + "protocols": [ + "tcp", + "udp" + ], + "transport": [ + "openvpn" + ], + "user_ips": false + }, + "host": "otter.demo.bitmask.net", + "ip_address": "46.165.242.169", + "location": "frankfurt" + }, + { + "capabilities": { + "adblock": false, + "filter_dns": false, + "limited": false, + "ports": [ + "443" + ], + "protocols": [ + "tcp", + "udp" + ], + "transport": [ + "openvpn" + ], + "user_ips": false + }, + "host": "orca.demo.bitmask.net", + "ip_address": "46.172.242.101", + "location": "moscow" + }, + { + "capabilities": { + "adblock": false, + "filter_dns": false, + "limited": false, + "ports": [ + "443" + ], + "protocols": [ + "tcp", + "udp" + ], + "transport": [ + "openvpn" + ], + "user_ips": false + }, + "host": "duck.demo.bitmask.net", + "ip_address": "104.165.142.132", + "location": "manila" + } + ], + "locations": { + "seattle__wa": { + "country_code": "US", + "hemisphere": "N", + "name": "Seattle, WA", + "timezone": "-7" + }, + "frankfurt": { + "country_code": "DE", + "hemisphere": "N", + "name": "Frankfurt", + "timezone": "+1" + }, + "moscow": { + "country_code": "RU", + "hemisphere": "N", + "name": "Moscow", + "timezone": "+3" + }, + "manila": { + "country_code": "PH", + "hemisphere": "N", + "name": "Manila", + "timezone": "+8" + } + }, + "openvpn_configuration": { + "auth": "SHA1", + "cipher": "AES-128-CBC", + "keepalive": "10 30", + "tls-cipher": "DHE-RSA-AES128-SHA", + "tun-ipv6": true + }, + "serial": 1, + "version": 1 +}
\ No newline at end of file |