summaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/AndroidManifest.xml4
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java29
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/Constants.java14
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java1
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/EipFragment.java122
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/MainActivity.java107
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java4
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java5
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java11
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java3
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java295
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EIP.java298
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java6
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java110
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/views/VpnStateImage.java16
-rw-r--r--app/src/main/res/drawable/ic_menu_color_point.pngbin0 -> 2454 bytes
-rw-r--r--app/src/main/res/drawable/ic_menu_default.pngbin0 -> 2454 bytes
-rw-r--r--app/src/main/res/values/strings.xml5
18 files changed, 704 insertions, 326 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 25300c8d..84fb83f8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -26,6 +26,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.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18"/>
@@ -106,7 +107,8 @@
<service
android:name=".eip.EIP"
- android:exported="false">
+ android:exported="false"
+ android:permission="android.permission.BIND_JOB_SERVICE">
<intent-filter>
<action android:name="se.leap.bitmaskclient.EIP.UPDATE"/>
<action android:name="se.leap.bitmaskclient.EIP.START"/>
diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
index aaff9ebc..bfc77261 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java
@@ -18,22 +18,20 @@ package se.leap.bitmaskclient;
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;
-import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import org.spongycastle.util.encoders.Base64;
-import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
@@ -51,13 +49,10 @@ import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
-import static android.R.attr.name;
import static se.leap.bitmaskclient.Constants.ALWAYS_ON_SHOW_DIALOG;
import static se.leap.bitmaskclient.Constants.DEFAULT_SHARED_PREFS_BATTERY_SAVER;
import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION;
@@ -442,4 +437,26 @@ public class ConfigHelper {
SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE);
return preferences.getBoolean(ALWAYS_ON_SHOW_DIALOG, true);
}
+
+ public static JSONObject getEipDefinitionFromPreferences(SharedPreferences preferences) {
+ JSONObject result = new JSONObject();
+ try {
+ String eipDefinitionString = preferences.getString(PROVIDER_EIP_DEFINITION, "");
+ if (!eipDefinitionString.isEmpty()) {
+ result = new JSONObject(eipDefinitionString);
+ }
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ public static void ensureNotOnMainThread(@NonNull Context context) throws IllegalStateException{
+ Looper looper = Looper.myLooper();
+ if (looper != null && looper == context.getMainLooper()) {
+ throw new IllegalStateException(
+ "calling this from your main thread can lead to deadlock");
+ }
+ }
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java
index c79ce87b..7fa09441 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Constants.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java
@@ -1,5 +1,7 @@
package se.leap.bitmaskclient;
+import android.text.TextUtils;
+
public interface Constants {
//////////////////////////////////////////////
@@ -88,4 +90,16 @@ public interface Constants {
// ICS-OPENVPN CONSTANTS
/////////////////////////////////////////////
String DEFAULT_SHARED_PREFS_BATTERY_SAVER = "screenoff";
+
+ //////////////////////////////////////////////
+ // CUSTOM CONSTANTS
+ /////////////////////////////////////////////
+ boolean ENABLE_DONATION = BuildConfig.enable_donation;
+ boolean ENABLE_DONATION_REMINDER = BuildConfig.enable_donation_reminder;
+ int DONATION_REMINDER_DURATION = BuildConfig.donation_reminder_duration;
+ String DONATION_URL = TextUtils.isEmpty(BuildConfig.donation_url) ?
+ BuildConfig.default_donation_url : BuildConfig.donation_url;
+ String LAST_DONATION_REMINDER_DATE = "last_donation_reminder_date";
+
+
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java b/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java
index 8238df55..01b10575 100644
--- a/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java
+++ b/app/src/main/java/se/leap/bitmaskclient/DrawerSettingsAdapter.java
@@ -40,6 +40,7 @@ public class DrawerSettingsAdapter extends BaseAdapter {
public static final int ABOUT = 2;
public static final int BATTERY_SAVER = 3;
public static final int ALWAYS_ON = 4;
+ public static final int DONATE = 5;
//view types
public final static int VIEW_SIMPLE_TEXT = 0;
diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java
index 9fcdcac9..535322e5 100644
--- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/EipFragment.java
@@ -25,20 +25,27 @@ import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
+import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
-import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.AppCompatTextView;
+import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.Locale;
import java.util.Observable;
import java.util.Observer;
@@ -56,17 +63,17 @@ 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.BROADCAST_EIP_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_STOP;
-import static se.leap.bitmaskclient.Constants.EIP_REQUEST;
+import static se.leap.bitmaskclient.Constants.DONATION_REMINDER_DURATION;
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_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.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.Constants.DONATION_URL;
+import static se.leap.bitmaskclient.Constants.ENABLE_DONATION;
+import static se.leap.bitmaskclient.Constants.ENABLE_DONATION_REMINDER;
+import static se.leap.bitmaskclient.Constants.LAST_DONATION_REMINDER_DATE;
+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;
@@ -108,6 +115,9 @@ public class EipFragment extends Fragment implements Observer {
private IOpenVPNServiceInternal mService;
private ServiceConnection openVpnConnection;
+ private final String DATE_PATTERN = "dd/MM/yyyy";
+ private final int ONE_DAY = 86400000; //1000*60*60*24
+
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -158,6 +168,14 @@ public class EipFragment extends Fragment implements Observer {
}
@Override
+ public void onStart() {
+ super.onStart();
+ if (isDonationReminderCallable()) {
+ showDonationReminder();
+ }
+ }
+
+ @Override
public void onResume() {
super.onResume();
//FIXME: avoid race conditions while checking certificate an logging in at about the same time
@@ -186,7 +204,6 @@ public class EipFragment extends Fragment implements Observer {
} else if (showPendingStartCancellation) {
outState.putBoolean(KEY_SHOW_PENDING_START_CANCELLATION, true);
alertDialog.dismiss();
-
}
}
@@ -240,7 +257,7 @@ public class EipFragment extends Fragment implements Observer {
askUserToLogIn(getString(vpn_certificate_user_message));
} else {
// provider has no VpnCertificate but user is logged in
- downloadVpnCertificate();
+ updateInvalidVpnCertificate();
}
}
@@ -280,24 +297,11 @@ public class EipFragment extends Fragment implements Observer {
protected void stopEipIfPossible() {
Context context = getContext();
- if (context != null) {
- if (isOpenVpnRunningWithoutNetwork()) {
- // TODO move to EIP
- // TODO see stopEIP function
- Bundle resultData = new Bundle();
- resultData.putString(EIP_REQUEST, EIP_ACTION_STOP);
- Intent intentUpdate = new Intent(BROADCAST_EIP_EVENT);
- intentUpdate.addCategory(Intent.CATEGORY_DEFAULT);
- intentUpdate.putExtra(BROADCAST_RESULT_CODE, Activity.RESULT_OK);
- intentUpdate.putExtra(BROADCAST_RESULT_KEY, resultData);
- Log.d(TAG, "sending broadcast");
- LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intentUpdate);
- } else {
- EipCommand.stopVPN(getContext());
- }
- } else {
+ if (context == null) {
Log.e(TAG, "context is null when trying to stop EIP");
+ return;
}
+ EipCommand.stopVPN(context);
}
private void askPendingStartCancellation() {
@@ -462,8 +466,8 @@ public class EipFragment extends Fragment implements Observer {
background.setImageAlpha(210);
}
- private void downloadVpnCertificate() {
- ProviderAPICommand.execute(getContext(), DOWNLOAD_VPN_CERTIFICATE, provider);
+ private void updateInvalidVpnCertificate() {
+ ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider);
}
private void askUserToLogIn(String userMessage) {
@@ -502,4 +506,68 @@ public class EipFragment extends Fragment implements Observer {
mService = null;
}
}
+
+ private void showDonationReminder() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ Log.e(TAG, "activity is null when triggering donation reminder");
+ return;
+ }
+ String message = TextUtils.isEmpty(activity.getString(R.string.donate_message)) ?
+ activity.getString(R.string.donate_default_message) : activity.getString(R.string.donate_message);
+ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(activity);
+ alertDialog = alertBuilder.setTitle(activity.getString(R.string.donate_title))
+ .setMessage(message)
+ .setPositiveButton(R.string.donate_button_donate, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL));
+ startActivity(browserIntent);
+ }
+ })
+ .setNegativeButton(R.string.donate_button_remind_later, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ }).setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ saveLastDonationReminderDate();
+ }
+ }).show();
+ }
+
+ private boolean isDonationReminderCallable() {
+ if (!ENABLE_DONATION || !ENABLE_DONATION_REMINDER) {
+ return false;
+ }
+
+ if (preferences == null) {
+ Log.e(TAG, "preferences is null!");
+ return false;
+ }
+
+ String lastDonationReminderDate = preferences.getString(LAST_DONATION_REMINDER_DATE, null);
+ if (lastDonationReminderDate == null) {
+ return true;
+ }
+
+ SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US);
+ Date lastDate;
+ try {
+ lastDate = sdf.parse(lastDonationReminderDate);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ Log.e(TAG, e.getMessage());
+ return false;
+ }
+
+ Date currentDate = new Date();
+ long diffDays = (currentDate.getTime() - lastDate.getTime()) / ONE_DAY;
+ return diffDays >= DONATION_REMINDER_DURATION;
+ }
+
+ private void saveLastDonationReminderDate() {
+ SimpleDateFormat sdf = new SimpleDateFormat(DATE_PATTERN, Locale.US);
+ Date lastDate = new Date();
+ preferences.edit().putString(LAST_DONATION_REMINDER_DATE, sdf.format(lastDate)).apply();
+ }
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java
index 4d57b6bc..868d2876 100644
--- a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java
@@ -18,15 +18,11 @@ package se.leap.bitmaskclient;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
@@ -39,17 +35,8 @@ import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
-import java.util.Observable;
-import java.util.Observer;
-
-import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
-import de.blinkt.openvpn.core.OpenVPNService;
-import de.blinkt.openvpn.core.ProfileManager;
-import de.blinkt.openvpn.core.VpnStatus;
import se.leap.bitmaskclient.drawer.NavigationDrawerFragment;
import se.leap.bitmaskclient.eip.EipCommand;
-import se.leap.bitmaskclient.eip.EipStatus;
-import se.leap.bitmaskclient.eip.VoidVpnService;
import se.leap.bitmaskclient.fragments.LogFragment;
import static android.content.Intent.CATEGORY_DEFAULT;
@@ -59,9 +46,7 @@ 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_ACTION_STOP_BLOCKING_VPN;
import static se.leap.bitmaskclient.Constants.EIP_REQUEST;
-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;
@@ -69,40 +54,24 @@ 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_DOWNLOADED_VPN_CERTIFICATE;
+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_DOWNLOADED_VPN_CERTIFICATE;
+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;
-public class MainActivity extends AppCompatActivity implements Observer {
+public class MainActivity extends AppCompatActivity {
public final static String TAG = MainActivity.class.getSimpleName();
private Provider provider = new Provider();
private SharedPreferences preferences;
- private EipStatus eipStatus;
private NavigationDrawerFragment navigationDrawerFragment;
private MainActivityBroadcastReceiver mainActivityBroadcastReceiver;
- private IOpenVPNServiceInternal mService;
- private ServiceConnection openVpnConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className,
- IBinder service) {
- mService = IOpenVPNServiceInternal.Stub.asInterface(service);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName arg0) {
- mService = null;
- }
-
- };
-
public final static String ACTION_SHOW_VPN_FRAGMENT = "action_show_vpn_fragment";
public final static String ACTION_SHOW_LOG_FRAGMENT = "action_show_log_fragment";
@@ -130,14 +99,28 @@ public class MainActivity extends AppCompatActivity implements Observer {
R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout));
- eipStatus = EipStatus.getInstance();
handleIntentAction(getIntent());
}
@Override
protected void onResume() {
super.onResume();
- bindOpenVpnService();
+ }
+
+ @Override
+ public void onBackPressed() {
+ FragmentManagerEnhanced fragmentManagerEnhanced = new FragmentManagerEnhanced(getSupportFragmentManager());
+ if (fragmentManagerEnhanced.findFragmentByTag(EipFragment.TAG) == null) {
+ Fragment fragment = new EipFragment();
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(PROVIDER_KEY, provider);
+ fragment.setArguments(bundle);
+ fragmentManagerEnhanced.beginTransaction()
+ .replace(R.id.container, fragment, EipFragment.TAG)
+ .commit();
+ } else {
+ super.onBackPressed();
+ }
}
@Override
@@ -152,10 +135,12 @@ public class MainActivity extends AppCompatActivity implements Observer {
return;
}
+ String fragmentTag = null;
Fragment fragment = null;
switch (intent.getAction()) {
case ACTION_SHOW_VPN_FRAGMENT:
fragment = new EipFragment();
+ fragmentTag = EipFragment.TAG;
Bundle bundle = new Bundle();
if (intent.hasExtra(ASK_TO_CANCEL_VPN)) {
bundle.putBoolean(ASK_TO_CANCEL_VPN, true);
@@ -175,7 +160,7 @@ public class MainActivity extends AppCompatActivity implements Observer {
if (fragment != null) {
new FragmentManagerEnhanced(getSupportFragmentManager()).beginTransaction()
- .replace(R.id.container, fragment)
+ .replace(R.id.container, fragment, fragmentTag)
.commit();
}
}
@@ -214,14 +199,13 @@ public class MainActivity extends AppCompatActivity implements Observer {
arguments.putParcelable(PROVIDER_KEY, provider);
fragment.setArguments(arguments);
new FragmentManagerEnhanced(getSupportFragmentManager()).beginTransaction()
- .replace(R.id.container, fragment)
+ .replace(R.id.container, fragment, EipFragment.TAG)
.commit();
}
@Override
protected void onPause() {
super.onPause();
- unbindService(openVpnConnection);
}
@Override
@@ -231,14 +215,6 @@ public class MainActivity extends AppCompatActivity implements Observer {
super.onDestroy();
}
-
- @Override
- public void update(Observable observable, Object data) {
- if (observable instanceof EipStatus) {
- eipStatus = (EipStatus) observable;
- }
- }
-
private void setUpBroadcastReceiver() {
IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_EIP_EVENT);
updateIntentFilter.addAction(BROADCAST_PROVIDER_API_EVENT);
@@ -299,7 +275,6 @@ public class MainActivity extends AppCompatActivity implements Observer {
case EIP_ACTION_STOP:
switch (resultCode) {
case RESULT_OK:
- stop();
break;
case RESULT_CANCELED:
break;
@@ -319,12 +294,12 @@ public class MainActivity extends AppCompatActivity implements Observer {
// TODO CATCH ME IF YOU CAN - WHAT DO WE WANT TO DO?
break;
- case CORRECTLY_DOWNLOADED_VPN_CERTIFICATE:
+ case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE:
provider = resultData.getParcelable(PROVIDER_KEY);
ConfigHelper.storeProviderInPreferences(preferences, provider);
EipCommand.startVPN(this, true);
break;
- case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE:
+ case INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE:
if (LeapSRPSession.loggedIn() || provider.allowsAnonymous()) {
showMainActivityErrorDialog(getString(downloading_vpn_certificate_failed));
} else {
@@ -360,38 +335,6 @@ public class MainActivity extends AppCompatActivity implements Observer {
}
- private void stop() {
- preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, false).apply();
- if (eipStatus.isBlockingVpnEstablished()) {
- stopBlockingVpn();
- }
- disconnect();
- }
-
- private void stopBlockingVpn() {
- Log.d(TAG, "stop VoidVpn!");
- Intent stopVoidVpnIntent = new Intent(this, VoidVpnService.class);
- stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN);
- startService(stopVoidVpnIntent);
- }
-
- private void disconnect() {
- ProfileManager.setConntectedVpnProfileDisconnected(this);
- if (mService != null) {
- try {
- mService.stopVPN(false);
- } catch (RemoteException e) {
- VpnStatus.logException(e);
- }
- }
- }
-
- private void bindOpenVpnService() {
- Intent intent = new Intent(this, OpenVPNService.class);
- intent.setAction(OpenVPNService.START_SERVICE);
- bindService(intent, openVpnConnection, Context.BIND_AUTO_CREATE);
- }
-
private void askUserToLogIn(String userMessage) {
Intent intent = new Intent(this, LoginActivity.class);
intent.putExtra(PROVIDER_KEY, provider);
diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java b/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java
index 23bc8427..1065503b 100644
--- a/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java
+++ b/app/src/main/java/se/leap/bitmaskclient/MainActivityErrorDialog.java
@@ -28,7 +28,7 @@ import org.json.JSONObject;
import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.DEFAULT;
import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.valueOf;
-import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.eip.EIP.ERRORS;
import static se.leap.bitmaskclient.eip.EIP.ERROR_ID;
@@ -107,7 +107,7 @@ public class MainActivityErrorDialog extends DialogFragment {
builder.setPositiveButton(R.string.update_certificate, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- ProviderAPICommand.execute(getContext(), DOWNLOAD_VPN_CERTIFICATE, provider);
+ ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider);
}
});
break;
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java
index f1f474d7..0e27592b 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java
@@ -45,6 +45,7 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase
LOG_IN = "srpAuth",
LOG_OUT = "logOut",
DOWNLOAD_VPN_CERTIFICATE = "downloadUserAuthedVPNCertificate",
+ UPDATE_INVALID_VPN_CERTIFICATE = "ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE",
PARAMETERS = "parameters",
RECEIVER_KEY = "receiver",
ERRORS = "errors",
@@ -67,7 +68,9 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase
PROVIDER_OK = 11,
PROVIDER_NOK = 12,
CORRECTLY_DOWNLOADED_EIP_SERVICE = 13,
- INCORRECTLY_DOWNLOADED_EIP_SERVICE = 14;
+ INCORRECTLY_DOWNLOADED_EIP_SERVICE = 14,
+ CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE = 15,
+ INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE = 16;
ProviderApiManager providerApiManager;
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java
index 2cde431e..753172e6 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java
@@ -66,6 +66,7 @@ import static se.leap.bitmaskclient.ProviderAPI.BACKEND_ERROR_KEY;
import static se.leap.bitmaskclient.ProviderAPI.BACKEND_ERROR_MESSAGE;
import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE;
import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.ProviderAPI.CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_SERVICE_JSON;
import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.ERRORID;
@@ -74,6 +75,7 @@ import static se.leap.bitmaskclient.ProviderAPI.FAILED_LOGIN;
import static se.leap.bitmaskclient.ProviderAPI.FAILED_SIGNUP;
import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE;
import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.LOGOUT_FAILED;
import static se.leap.bitmaskclient.ProviderAPI.LOG_IN;
import static se.leap.bitmaskclient.ProviderAPI.LOG_OUT;
@@ -87,6 +89,7 @@ import static se.leap.bitmaskclient.ProviderAPI.SIGN_UP;
import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_LOGIN;
import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_LOGOUT;
import static se.leap.bitmaskclient.ProviderAPI.SUCCESSFUL_SIGNUP;
+import static se.leap.bitmaskclient.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.ProviderAPI.UPDATE_PROVIDER_DETAILS;
import static se.leap.bitmaskclient.ProviderAPI.USER_MESSAGE;
import static se.leap.bitmaskclient.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING;
@@ -197,6 +200,14 @@ public abstract class ProviderApiManagerBase {
sendToReceiverOrBroadcast(receiver, INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE, result, provider);
}
break;
+ case UPDATE_INVALID_VPN_CERTIFICATE:
+ result = updateVpnCertificate(provider);
+ if (result.getBoolean(BROADCAST_RESULT_KEY)) {
+ sendToReceiverOrBroadcast(receiver, CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE, result, provider);
+ } else {
+ sendToReceiverOrBroadcast(receiver, INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE, result, provider);
+ }
+ break;
case DOWNLOAD_SERVICE_JSON:
result = getAndSetEipServiceJson(provider);
if (result.getBoolean(BROADCAST_RESULT_KEY)) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java
index 168fb02e..a31bd9ee 100644
--- a/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/ProviderListBaseActivity.java
@@ -392,7 +392,8 @@ public abstract class ProviderListBaseActivity extends ConfigWizardBaseActivity
Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY);
Provider handledProvider = resultData.getParcelable(PROVIDER_KEY);
- if (handledProvider != null && handledProvider.getDomain().equalsIgnoreCase(provider.getDomain())) {
+ if (handledProvider != null && provider != null &&
+ handledProvider.getDomain().equalsIgnoreCase(provider.getDomain())) {
switch (resultCode) {
case PROVIDER_OK:
handleProviderSetUp(handledProvider);
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 9256c136..be4bdf99 100644
--- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java
@@ -22,8 +22,11 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
@@ -46,18 +49,17 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CompoundButton;
import android.widget.ListView;
-import android.widget.Toast;
import se.leap.bitmaskclient.ConfigHelper;
import se.leap.bitmaskclient.DrawerSettingsAdapter;
import se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem;
-import se.leap.bitmaskclient.FragmentManagerEnhanced;
-import se.leap.bitmaskclient.fragments.AlwaysOnDialog;
import se.leap.bitmaskclient.EipFragment;
+import se.leap.bitmaskclient.FragmentManagerEnhanced;
import se.leap.bitmaskclient.Provider;
import se.leap.bitmaskclient.ProviderListActivity;
import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.fragments.AboutFragment;
+import se.leap.bitmaskclient.fragments.AlwaysOnDialog;
import se.leap.bitmaskclient.fragments.LogFragment;
import static android.content.Context.MODE_PRIVATE;
@@ -67,9 +69,12 @@ import static se.leap.bitmaskclient.ConfigHelper.getShowAlwaysOnDialog;
import static se.leap.bitmaskclient.Constants.PROVIDER_KEY;
import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER;
import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
+import static se.leap.bitmaskclient.Constants.DONATION_URL;
+import static se.leap.bitmaskclient.Constants.ENABLE_DONATION;
import static se.leap.bitmaskclient.DrawerSettingsAdapter.ABOUT;
import static se.leap.bitmaskclient.DrawerSettingsAdapter.ALWAYS_ON;
import static se.leap.bitmaskclient.DrawerSettingsAdapter.BATTERY_SAVER;
+import static se.leap.bitmaskclient.DrawerSettingsAdapter.DONATE;
import static se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem.getSimpleTextInstance;
import static se.leap.bitmaskclient.DrawerSettingsAdapter.DrawerSettingsItem.getSwitchInstance;
import static se.leap.bitmaskclient.DrawerSettingsAdapter.LOG;
@@ -77,6 +82,7 @@ import static se.leap.bitmaskclient.DrawerSettingsAdapter.SWITCH_PROVIDER;
import static se.leap.bitmaskclient.R.string.about_fragment_title;
import static se.leap.bitmaskclient.R.string.log_fragment_title;
import static se.leap.bitmaskclient.R.string.switch_provider_menu_option;
+import static se.leap.bitmaskclient.R.string.donate_title;
/**
* Fragment used for managing interactions for and presentation of a navigation drawer.
@@ -90,24 +96,28 @@ public class NavigationDrawerFragment extends Fragment {
* expands it. This shared preference tracks this.
*/
private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned";
+ private static final String TAG = NavigationDrawerFragment.class.getName();
+ public static final int TWO_SECONDS = 2000;
+ public static final int THREE_SECONDS = 3500;
/**
* Helper component that ties the action bar to the navigation drawer.
*/
- private ActionBarDrawerToggle mDrawerToggle;
+ private ActionBarDrawerToggle drawerToggle;
- private DrawerLayout mDrawerLayout;
- private View mDrawerView;
- private ListView mDrawerSettingsListView;
- private ListView mDrawerAccountsListView;
- private View mFragmentContainerView;
+ private DrawerLayout drawerLayout;
+ private View drawerView;
+ private ListView drawerAccountsListView;
+ private View fragmentContainerView;
private ArrayAdapter<String> accountListAdapter;
private DrawerSettingsAdapter settingsListAdapter;
+ private Toolbar toolbar;
- private boolean mFromSavedInstanceState;
- private boolean mUserLearnedDrawer;
+ private boolean userLearnedDrawer;
+ private volatile boolean wasPaused;
+ private volatile boolean shouldCloseOnResume;
- private String mTitle;
+ private String title;
private SharedPreferences preferences;
@@ -115,38 +125,48 @@ public class NavigationDrawerFragment extends Fragment {
private boolean showEnableExperimentalFeature = false;
AlertDialog alertDialog;
- public NavigationDrawerFragment() {
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Read in the flag indicating whether or not the user has demonstrated awareness of the
+ // Reads in the flag indicating whether or not the user has demonstrated awareness of the
// drawer. See PREF_USER_LEARNED_DRAWER for details.
preferences = getContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
- mUserLearnedDrawer = preferences.getBoolean(PREF_USER_LEARNED_DRAWER, false);
- if (savedInstanceState != null) {
- mFromSavedInstanceState = true;
- }
+ userLearnedDrawer = preferences.getBoolean(PREF_USER_LEARNED_DRAWER, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- // Indicate that this fragment would like to influence the set of actions in the action bar.
+ // Indicates that this fragment would like to influence the set of actions in the action bar.
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- mDrawerView = inflater.inflate(R.layout.f_drawer_main, container, false);
+ drawerView = inflater.inflate(R.layout.f_drawer_main, container, false);
restoreFromSavedInstance(savedInstanceState);
- return mDrawerView;
+ return drawerView;
}
public boolean isDrawerOpen() {
- return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView);
+ return drawerLayout != null && drawerLayout.isDrawerOpen(fragmentContainerView);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ wasPaused = false;
+ if (shouldCloseOnResume) {
+ closeDrawerWithDelay();
+ showDottedIconWithDelay();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ wasPaused = true;
}
/**
@@ -156,67 +176,44 @@ public class NavigationDrawerFragment extends Fragment {
* @param drawerLayout The DrawerLayout containing this fragment's UI.
*/
public void setUp(int fragmentId, DrawerLayout drawerLayout) {
- AppCompatActivity activity = (AppCompatActivity) getActivity();
- ActionBar actionBar = activity.getSupportActionBar();
-
- mDrawerSettingsListView = mDrawerView.findViewById(R.id.settingsList);
- mDrawerSettingsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- selectItem(parent, position);
- }
- });
- settingsListAdapter = new DrawerSettingsAdapter(getLayoutInflater());
- if (getContext() != null) {
- 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);
- }
- }));
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- settingsListAdapter.addItem(getSimpleTextInstance(getString(R.string.always_on_vpn), ALWAYS_ON));
- }
- settingsListAdapter.addItem(getSimpleTextInstance(getString(switch_provider_menu_option), SWITCH_PROVIDER));
- settingsListAdapter.addItem(getSimpleTextInstance(getString(log_fragment_title), LOG));
- settingsListAdapter.addItem(getSimpleTextInstance(getString(about_fragment_title), ABOUT));
-
- mDrawerSettingsListView.setAdapter(settingsListAdapter);
-
- mDrawerAccountsListView = mDrawerView.findViewById(R.id.accountList);
- mDrawerAccountsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- selectItem(parent, position);
- }
- });
+ final AppCompatActivity activity = (AppCompatActivity) getActivity();
+ fragmentContainerView = activity.findViewById(fragmentId);
+ this.drawerLayout = drawerLayout;
+ // set a custom shadow that overlays the main content when the drawer opens
+ this.drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+ toolbar = this.drawerLayout.findViewById(R.id.toolbar);
+ final ActionBar actionBar = setupActionBar();
+ setupSettingsListAdapter();
+ setupSettingsListView();
accountListAdapter = new ArrayAdapter<>(actionBar.getThemedContext(),
R.layout.v_single_list_item,
android.R.id.text1);
+ refreshAccountListAdapter();
+ setupAccountsListView();
+ setupActionBarDrawerToggle(activity);
- createListAdapterData();
-
- mDrawerAccountsListView.setAdapter(accountListAdapter);
-
- mFragmentContainerView = activity.findViewById(fragmentId);
- mDrawerLayout = drawerLayout;
+ if (!userLearnedDrawer) {
+ openNavigationDrawerForFirstTimeUsers();
+ }
- // set a custom shadow that overlays the main content when the drawer opens
- mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+ // Defer code dependent on restoration of previous instance state.
+ this.drawerLayout.post(new Runnable() {
+ @Override
+ public void run() {
+ drawerToggle.syncState();
+ }
+ });
+ this.drawerLayout.addDrawerListener(drawerToggle);
+ }
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setHomeButtonEnabled(true);
+ private void setupActionBarDrawerToggle(final AppCompatActivity activity) {
// ActionBarDrawerToggle ties together the the proper interactions
// between the navigation drawer and the action bar app icon.
- mDrawerToggle = new ActionBarDrawerToggle(
- getActivity(),
- mDrawerLayout,
- (Toolbar) drawerLayout.findViewById(R.id.toolbar),
+ drawerToggle = new ActionBarDrawerToggle(
+ activity,
+ drawerLayout,
+ toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close
) {
@@ -226,8 +223,7 @@ public class NavigationDrawerFragment extends Fragment {
if (!isAdded()) {
return;
}
-
- getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
+ activity.invalidateOptionsMenu();
}
@Override
@@ -237,40 +233,120 @@ public class NavigationDrawerFragment extends Fragment {
return;
}
- if (!mUserLearnedDrawer) {
+ if (!userLearnedDrawer) {
// The user manually opened the drawer; store this flag to prevent auto-showing
// the navigation drawer automatically in the future.
- mUserLearnedDrawer = true;
+ userLearnedDrawer = true;
preferences.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply();
+ toolbar.setNavigationIcon(R.drawable.ic_menu_default);
}
-
- getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
+ activity.invalidateOptionsMenu();
}
};
+ }
+
+ private void setupAccountsListView() {
+ drawerAccountsListView = drawerView.findViewById(R.id.accountList);
+ drawerAccountsListView.setAdapter(accountListAdapter);
+ drawerAccountsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ selectItem(parent, position);
+ }
+ });
+ }
+
+ 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.setAdapter(settingsListAdapter);
+ }
- // If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer,
- // per the navigation drawer design guidelines.
- if (!mUserLearnedDrawer && !mFromSavedInstanceState) {
- mDrawerLayout.openDrawer(mFragmentContainerView);
+ private void setupSettingsListAdapter() {
+ settingsListAdapter = new DrawerSettingsAdapter(getLayoutInflater());
+ if (getContext() != null) {
+ 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);
+ }
+ }));
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ settingsListAdapter.addItem(getSimpleTextInstance(getString(R.string.always_on_vpn), ALWAYS_ON));
}
+ settingsListAdapter.addItem(getSimpleTextInstance(getString(switch_provider_menu_option), SWITCH_PROVIDER));
+ settingsListAdapter.addItem(getSimpleTextInstance(getString(log_fragment_title), LOG));
+ if (ENABLE_DONATION) {
+ settingsListAdapter.addItem(getSimpleTextInstance(getString(donate_title), DONATE));
+ }
+ settingsListAdapter.addItem(getSimpleTextInstance(getString(about_fragment_title), ABOUT));
+ }
- // Defer code dependent on restoration of previous instance state.
- mDrawerLayout.post(new Runnable() {
+ private ActionBar setupActionBar() {
+ AppCompatActivity activity = (AppCompatActivity) getActivity();
+ activity.setSupportActionBar(toolbar);
+ final ActionBar actionBar = activity.getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeButtonEnabled(true);
+ return actionBar;
+ }
+
+ private void openNavigationDrawerForFirstTimeUsers() {
+ if (userLearnedDrawer) {
+ return;
+ }
+
+ drawerLayout.openDrawer(fragmentContainerView, false);
+ closeDrawerWithDelay();
+ showDottedIconWithDelay();
+
+ }
+
+ private void showDottedIconWithDelay() {
+ final Handler navigationDrawerHandler = new Handler();
+ navigationDrawerHandler.postDelayed(new Runnable() {
@Override
public void run() {
- mDrawerToggle.syncState();
+ if (!wasPaused) {
+ toolbar.setNavigationIcon(R.drawable.ic_menu_color_point);
+ toolbar.playSoundEffect(android.view.SoundEffectConstants.CLICK);
+ }
+
}
- });
- mDrawerLayout.addDrawerListener(mDrawerToggle);
+ }, 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;
+ }
+ }
+ }, TWO_SECONDS);
}
private void selectItem(AdapterView<?> list, int position) {
if (list != null) {
((ListView) list).setItemChecked(position, true);
}
- if (mDrawerLayout != null) {
- mDrawerLayout.closeDrawer(mFragmentContainerView);
+ if (drawerLayout != null) {
+ drawerLayout.closeDrawer(fragmentContainerView);
}
onTextItemSelected(list, position);
}
@@ -349,12 +425,12 @@ public class NavigationDrawerFragment extends Fragment {
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Forward the new configuration the drawer toggle component.
- mDrawerToggle.onConfigurationChanged(newConfig);
+ drawerToggle.onConfigurationChanged(newConfig);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- if (mDrawerLayout != null && isDrawerOpen()) {
+ if (drawerLayout != null && isDrawerOpen()) {
showGlobalContextActionBar();
}
super.onCreateOptionsMenu(menu, inflater);
@@ -362,10 +438,9 @@ public class NavigationDrawerFragment extends Fragment {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- if (mDrawerToggle.onOptionsItemSelected(item)) {
+ if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
-
return super.onOptionsItemSelected(item);
}
@@ -418,27 +493,29 @@ public class NavigationDrawerFragment extends Fragment {
// update the main content by replacing fragments
FragmentManager fragmentManager = getFragmentManager();
Fragment fragment = null;
+ String fragmentTag = null;
- if (parent == mDrawerAccountsListView) {
- mTitle = getString(R.string.vpn_fragment_title);
+ if (parent == drawerAccountsListView) {
+ title = getString(R.string.vpn_fragment_title);
fragment = new EipFragment();
+ fragmentTag = EipFragment.TAG;
Bundle arguments = new Bundle();
Provider currentProvider = ConfigHelper.getSavedProviderFromSharedPreferences(preferences);
arguments.putParcelable(PROVIDER_KEY, currentProvider);
fragment.setArguments(arguments);
} else {
- Log.d("Drawer", String.format("Selected position %d", position));
+ Log.d(TAG, String.format("Selected position %d", position));
DrawerSettingsItem settingsItem = settingsListAdapter.getItem(position);
switch (settingsItem.getItemType()) {
case SWITCH_PROVIDER:
getActivity().startActivityForResult(new Intent(getActivity(), ProviderListActivity.class), REQUEST_CODE_SWITCH_PROVIDER);
break;
case LOG:
- mTitle = getString(log_fragment_title);
+ title = getString(log_fragment_title);
fragment = new LogFragment();
break;
case ABOUT:
- mTitle = getString(about_fragment_title);
+ title = getString(about_fragment_title);
fragment = new AboutFragment();
break;
case ALWAYS_ON:
@@ -450,6 +527,10 @@ public class NavigationDrawerFragment extends Fragment {
startActivity(intent);
}
break;
+ case DONATE:
+ Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL));
+ startActivity(browserIntent);
+ break;
default:
break;
}
@@ -457,7 +538,7 @@ public class NavigationDrawerFragment extends Fragment {
if (fragment != null) {
fragmentManager.beginTransaction()
- .replace(R.id.container, fragment)
+ .replace(R.id.container, fragment, fragmentTag)
.commit();
}
@@ -468,18 +549,18 @@ public class NavigationDrawerFragment extends Fragment {
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(true);
- actionBar.setSubtitle(mTitle);
+ actionBar.setSubtitle(title);
}
}
public void refresh() {
- createListAdapterData();
+ refreshAccountListAdapter();
accountListAdapter.notifyDataSetChanged();
- mDrawerAccountsListView.setAdapter(accountListAdapter);
+ drawerAccountsListView.setAdapter(accountListAdapter);
}
- private void createListAdapterData() {
+ private void refreshAccountListAdapter() {
accountListAdapter.clear();
String providerName = ConfigHelper.getProviderName(preferences);
if (providerName == null) {
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 665e0ebd..159bc9a7 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
@@ -16,25 +16,44 @@
*/
package se.leap.bitmaskclient.eip;
-import android.app.IntentService;
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
+import android.support.annotation.WorkerThread;
+import android.support.v4.app.JobIntentService;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
+import java.io.Closeable;
import java.lang.ref.WeakReference;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import de.blinkt.openvpn.LaunchVPN;
+import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
+import de.blinkt.openvpn.core.OpenVPNService;
+import de.blinkt.openvpn.core.ProfileManager;
+import de.blinkt.openvpn.core.VpnStatus;
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 se.leap.bitmaskclient.ConfigHelper.ensureNotOnMainThread;
import static se.leap.bitmaskclient.Constants.BROADCAST_EIP_EVENT;
import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_CODE;
import static se.leap.bitmaskclient.Constants.BROADCAST_RESULT_KEY;
@@ -43,11 +62,11 @@ import static se.leap.bitmaskclient.Constants.EIP_ACTION_IS_RUNNING;
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_ACTION_STOP_BLOCKING_VPN;
import static se.leap.bitmaskclient.Constants.EIP_EARLY_ROUTES;
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;
-import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION;
import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
import static se.leap.bitmaskclient.MainActivityErrorDialog.DOWNLOAD_ERRORS.ERROR_INVALID_VPN_CERTIFICATE;
@@ -56,44 +75,81 @@ import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid;
/**
* EIP is the abstract base class for interacting with and managing the Encrypted
* Internet Proxy connection. Connections are started, stopped, and queried through
- * this IntentService.
+ * this Service.
* Contains logic for parsing eip-service.json from the provider, configuring and selecting
* gateways, and controlling {@link de.blinkt.openvpn.core.OpenVPNService} connections.
*
* @author Sean Leonard <meanderingcode@aetherislands.net>
* @author Parménides GV <parmegv@sdf.org>
*/
-public final class EIP extends IntentService {
+public final class EIP extends JobIntentService implements Observer {
+
public final static String TAG = EIP.class.getSimpleName(),
SERVICE_API_PATH = "config/eip-service.json",
ERRORS = "errors",
ERROR_ID = "errorID";
- private WeakReference<ResultReceiver> mReceiverRef = new WeakReference<>(null);
- private SharedPreferences preferences;
+ private volatile SharedPreferences preferences;
+ private volatile EipStatus eipStatus;
+ // Service connection to OpenVpnService, shared between threads
+ private volatile OpenVpnServiceConnection openVpnServiceConnection;
+ private WeakReference<ResultReceiver> mResultRef = new WeakReference<>(null);
+
+ /**
+ * Unique job ID for this service.
+ */
+ static final int JOB_ID = 1312;
- public EIP() {
- super(TAG);
+ /**
+ * Convenience method for enqueuing work in to this service.
+ */
+ static void enqueueWork(Context context, Intent work) {
+ enqueueWork(context, EIP.class, JOB_ID, work);
}
@Override
public void onCreate() {
super.onCreate();
+ eipStatus = EipStatus.getInstance();
+ eipStatus.addObserver(this);
preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
}
@Override
- protected void onHandleIntent(Intent intent) {
- String action = intent.getAction();
- if (intent.getParcelableExtra(EIP_RECEIVER) != null) {
- mReceiverRef = new WeakReference<>((ResultReceiver) intent.getParcelableExtra(EIP_RECEIVER));
+ public void onDestroy() {
+ super.onDestroy();
+ eipStatus.deleteObserver(this);
+ if (openVpnServiceConnection != null) {
+ openVpnServiceConnection.close();
+ openVpnServiceConnection = null;
+ }
+ }
+
+ /**
+ * update eipStatus whenever it changes
+ */
+ @Override
+ public void update(Observable observable, Object data) {
+ if (observable instanceof EipStatus) {
+ eipStatus = (EipStatus) observable;
}
+ }
+ /**
+ *
+ * @param intent the intent that started this EIP call
+ */
+ @Override
+ protected void onHandleWork(@NonNull Intent intent) {
+ final String action = intent.getAction();
if (action == null) {
return;
}
+ if (intent.getParcelableExtra(EIP_RECEIVER) != null) {
+ mResultRef = new WeakReference<>((ResultReceiver) intent.getParcelableExtra(EIP_RECEIVER));
+ }
switch (action) {
case EIP_ACTION_START:
boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, true);
@@ -119,21 +175,21 @@ public final class EIP extends IntentService {
* Intent to {@link de.blinkt.openvpn.LaunchVPN}.
* It also sets up early routes.
*/
+ @SuppressLint("ApplySharedPref")
private void startEIP(boolean earlyRoutes) {
- if (!EipStatus.getInstance().isBlockingVpnEstablished() && earlyRoutes) {
+ if (!eipStatus.isBlockingVpnEstablished() && earlyRoutes) {
earlyRoutes();
}
Bundle result = new Bundle();
-
- if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)){
+ if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)) {
preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, true).commit();
}
GatewaysManager gatewaysManager = gatewaysFromPreferences();
- if (!isVPNCertificateValid()){
+ if (!isVPNCertificateValid()) {
setErrorResult(result, vpn_certificate_is_invalid, ERROR_INVALID_VPN_CERTIFICATE.toString());
- tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_CANCELED, result);
+ tellToReceiverOrBroadcast(EIP_ACTION_START, RESULT_CANCELED);
return;
}
@@ -173,6 +229,11 @@ public final class EIP extends IntentService {
startActivity(voidVpnLauncher);
}
+ /**
+ * starts the VPN and connects to the given gateway
+ *
+ * @param gateway to connect to
+ */
private void launchActiveGateway(Gateway gateway) {
Intent intent = new Intent(this, LaunchVPN.class);
intent.setAction(Intent.ACTION_MAIN);
@@ -182,14 +243,13 @@ public final class EIP extends IntentService {
startActivity(intent);
}
+ /**
+ * Stop VPN
+ * First checks if the OpenVpnConnection is open then
+ * terminates EIP if currently connected or connecting
+ */
private void stopEIP() {
- // TODO stop eip from here if possible...
- // TODO then refactor EipFragment.handleSwitchOff
- EipStatus eipStatus = EipStatus.getInstance();
- int resultCode = RESULT_CANCELED;
- if (eipStatus.isConnected() || eipStatus.isConnecting())
- resultCode = RESULT_OK;
-
+ int resultCode = stop() ? RESULT_OK : RESULT_CANCELED;
tellToReceiverOrBroadcast(EIP_ACTION_STOP, resultCode);
}
@@ -199,36 +259,27 @@ public final class EIP extends IntentService {
* request if it's not connected, <code>Activity.RESULT_OK</code> otherwise.
*/
private void isRunning() {
- EipStatus eipStatus = EipStatus.getInstance();
int resultCode = (eipStatus.isConnected()) ?
RESULT_OK :
RESULT_CANCELED;
tellToReceiverOrBroadcast(EIP_ACTION_IS_RUNNING, resultCode);
}
- private JSONObject eipDefinitionFromPreferences() {
- JSONObject result = new JSONObject();
- try {
- String eipDefinitionString = preferences.getString(PROVIDER_EIP_DEFINITION, "");
- if (!eipDefinitionString.isEmpty()) {
- result = new JSONObject(eipDefinitionString);
- }
- } catch (JSONException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return result;
- }
-
+ /**
+ * read eipServiceJson from preferences and parse Gateways
+ *
+ * @return GatewaysManager
+ */
private GatewaysManager gatewaysFromPreferences() {
GatewaysManager gatewaysManager = new GatewaysManager(this, preferences);
- //TODO: THIS IS A QUICK FIX - it deletes all profiles in ProfileManager, thus it's possible
- // to add all gateways from prefs without duplicates, but this should be refactored.
- gatewaysManager.clearGatewaysAndProfiles();
- gatewaysManager.fromEipServiceJson(eipDefinitionFromPreferences());
+ gatewaysManager.configureFromPreferences();
return gatewaysManager;
}
+ /**
+ * read VPN certificate from preferences and check it
+ * broadcast result
+ */
private void checkVPNCertificateValidity() {
int resultCode = isVPNCertificateValid() ?
RESULT_OK :
@@ -236,25 +287,51 @@ public final class EIP extends IntentService {
tellToReceiverOrBroadcast(EIP_ACTION_CHECK_CERT_VALIDITY, resultCode);
}
+ /**
+ * read VPN certificate from preferences and check it
+ *
+ * @return true if VPN certificate is valid false otherwise
+ */
private boolean isVPNCertificateValid() {
VpnCertificateValidator validator = new VpnCertificateValidator(preferences.getString(PROVIDER_VPN_CERTIFICATE, ""));
return validator.isValid();
}
+ /**
+ * send resultCode and resultData to receiver or
+ * broadcast the result if no receiver is defined
+ *
+ * @param action the action that has been performed
+ * @param resultCode RESULT_OK if action was successful RESULT_CANCELED otherwise
+ * @param resultData other data to broadcast or return to receiver
+ */
private void tellToReceiverOrBroadcast(String action, int resultCode, Bundle resultData) {
resultData.putString(EIP_REQUEST, action);
- if (mReceiverRef.get() != null) {
- mReceiverRef.get().send(resultCode, resultData);
+ if (mResultRef.get() != null) {
+ mResultRef.get().send(resultCode, resultData);
} else {
broadcastEvent(resultCode, resultData);
}
}
+ /**
+ * send resultCode and resultData to receiver or
+ * broadcast the result if no receiver is defined
+ *
+ * @param action the action that has been performed
+ * @param resultCode RESULT_OK if action was successful RESULT_CANCELED otherwise
+ */
private void tellToReceiverOrBroadcast(String action, int resultCode) {
tellToReceiverOrBroadcast(action, resultCode, new Bundle());
}
- private void broadcastEvent(int resultCode , Bundle resultData) {
+ /**
+ * broadcast result
+ *
+ * @param resultCode RESULT_OK if action was successful RESULT_CANCELED otherwise
+ * @param resultData other data to broadcast or return to receiver
+ */
+ private void broadcastEvent(int resultCode, Bundle resultData) {
Intent intentUpdate = new Intent(BROADCAST_EIP_EVENT);
intentUpdate.addCategory(CATEGORY_DEFAULT);
intentUpdate.putExtra(BROADCAST_RESULT_CODE, resultCode);
@@ -263,20 +340,133 @@ public final class EIP extends IntentService {
LocalBroadcastManager.getInstance(this).sendBroadcast(intentUpdate);
}
- Bundle setErrorResult(Bundle result, int errorMessageId, String errorId) {
+
+ /**
+ * helper function to add error to result bundle
+ *
+ * @param result - result of an action
+ * @param errorMessageId - id of string resource describing the error
+ * @param errorId - MainActivityErrorDialog DownloadError id
+ */
+ void setErrorResult(Bundle result, @StringRes int errorMessageId, String errorId) {
JSONObject errorJson = new JSONObject();
- addErrorMessageToJson(errorJson, getResources().getString(errorMessageId), errorId);
+ try {
+ errorJson.put(ERRORS, getResources().getString(errorMessageId));
+ errorJson.put(ERROR_ID, errorId);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
result.putString(ERRORS, errorJson.toString());
result.putBoolean(BROADCAST_RESULT_KEY, false);
- return result;
}
- private void addErrorMessageToJson(JSONObject jsonObject, String errorMessage, String errorId) {
+
+ /**
+ * disable Bitmask starting on after phone reboot
+ * then stop VPN
+ */
+ private boolean stop() {
+ preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, false).apply();
+ if (eipStatus.isBlockingVpnEstablished()) {
+ stopBlockingVpn();
+ }
+ return disconnect();
+ }
+
+ /**
+ * stop void vpn from blocking internet
+ */
+ private void stopBlockingVpn() {
+ Log.d(TAG, "stop VoidVpn!");
+ Intent stopVoidVpnIntent = new Intent(this, VoidVpnService.class);
+ stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN);
+ startService(stopVoidVpnIntent);
+ }
+
+
+ /**
+ * creates a OpenVpnServiceConnection if necessary
+ * then terminates OpenVPN
+ */
+ private boolean disconnect() {
try {
- jsonObject.put(ERRORS, errorMessage);
- jsonObject.put(ERROR_ID, errorId);
- } catch (JSONException e) {
- e.printStackTrace();
+ initOpenVpnServiceConnection();
+ } catch (InterruptedException | IllegalStateException e) {
+ return false;
+ }
+
+ ProfileManager.setConntectedVpnProfileDisconnected(this);
+ try {
+ return openVpnServiceConnection.getService().stopVPN(false);
+ } catch (RemoteException e) {
+ VpnStatus.logException(e);
}
+ return false;
}
+
+ /**
+ * Assigns a new OpenVpnServiceConnection to EIP's member variable openVpnServiceConnection.
+ * Only one thread at a time can create the service connection, that will be shared between threads
+ *
+ * @throws InterruptedException thrown if thread gets interrupted
+ * @throws IllegalStateException thrown if this method was not called from a background thread
+ */
+ private void initOpenVpnServiceConnection() throws InterruptedException, IllegalStateException {
+ if (openVpnServiceConnection == null) {
+ Log.d(TAG, "serviceConnection is still null");
+ openVpnServiceConnection = new OpenVpnServiceConnection(this);
+ }
+ }
+
+ /**
+ * Creates a service connection to OpenVpnService.
+ * The constructor blocks until the service is bound to the given Context.
+ * Pattern stolen from android.security.KeyChain.java
+ */
+ @WorkerThread
+ public static class OpenVpnServiceConnection implements Closeable {
+ private final Context context;
+ private ServiceConnection serviceConnection;
+ private IOpenVPNServiceInternal service;
+
+ OpenVpnServiceConnection(Context context) throws InterruptedException, IllegalStateException {
+ this.context = context;
+ ensureNotOnMainThread(context);
+ Log.d(TAG, "initSynchronizedServiceConnection!");
+ initSynchronizedServiceConnection(context);
+ }
+
+ private void initSynchronizedServiceConnection(final Context context) throws InterruptedException {
+ final BlockingQueue<IOpenVPNServiceInternal> blockingQueue = new LinkedBlockingQueue<>(1);
+ this.serviceConnection = new ServiceConnection() {
+ volatile boolean mConnectedAtLeastOnce = false;
+ @Override public void onServiceConnected(ComponentName name, IBinder service) {
+ if (!mConnectedAtLeastOnce) {
+ mConnectedAtLeastOnce = true;
+ try {
+ blockingQueue.put(IOpenVPNServiceInternal.Stub.asInterface(service));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ @Override public void onServiceDisconnected(ComponentName name) {
+ }
+ };
+
+ Intent intent = new Intent(context, OpenVPNService.class);
+ intent.setAction(OpenVPNService.START_SERVICE);
+ context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+ service = blockingQueue.take();
+ }
+
+ @Override public void close() {
+ context.unbindService(serviceConnection);
+ }
+
+ public IOpenVPNServiceInternal getService() {
+ return service;
+ }
+ }
+
}
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 d2c8b4fc..19735483 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java
@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.ResultReceiver;
import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -41,7 +42,7 @@ public class EipCommand {
vpnIntent.setAction(action);
if (resultReceiver != null)
vpnIntent.putExtra(EIP_RECEIVER, resultReceiver);
- context.startService(vpnIntent);
+ EIP.enqueueWork(context, vpnIntent);
}
public static void startVPN(@NonNull Context context, boolean earlyRoutes) {
@@ -50,6 +51,7 @@ public class EipCommand {
execute(context, EIP_ACTION_START, null, baseIntent);
}
+ @VisibleForTesting
public static void startVPN(@NonNull Context context, ResultReceiver resultReceiver) {
execute(context, EIP_ACTION_START, resultReceiver, null);
}
@@ -58,6 +60,7 @@ public class EipCommand {
execute(context, EIP_ACTION_STOP);
}
+ @VisibleForTesting
public static void stopVPN(@NonNull Context context, ResultReceiver resultReceiver) {
execute(context, EIP_ACTION_STOP, resultReceiver, null);
}
@@ -66,6 +69,7 @@ public class EipCommand {
execute(context, EIP_ACTION_CHECK_CERT_VALIDITY);
}
+ @VisibleForTesting
public static void checkVpnCertificate(@NonNull Context context, ResultReceiver resultReceiver) {
execute(context, EIP_ACTION_CHECK_CERT_VALIDITY, resultReceiver, 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 1bdb53ab..a04ede08 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.SharedPreferences;
import com.google.gson.Gson;
-import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import org.json.JSONArray;
@@ -36,6 +35,7 @@ import java.util.List;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.Connection;
import de.blinkt.openvpn.core.ProfileManager;
+import se.leap.bitmaskclient.ConfigHelper;
import se.leap.bitmaskclient.Provider;
import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY;
@@ -49,52 +49,49 @@ public class GatewaysManager {
private Context context;
private SharedPreferences preferences;
private List<Gateway> gateways = new ArrayList<>();
- private ProfileManager profile_manager;
- private Type list_type = new TypeToken<ArrayList<Gateway>>() {
- }.getType();
+ private ProfileManager profileManager;
+ private Type listType = new TypeToken<ArrayList<Gateway>>() {}.getType();
- public GatewaysManager() {
- }
-
- public GatewaysManager(Context context, SharedPreferences preferences) {
+ GatewaysManager(Context context, SharedPreferences preferences) {
this.context = context;
this.preferences = preferences;
- profile_manager = ProfileManager.getInstance(context);
+ profileManager = ProfileManager.getInstance(context);
}
+ /**
+ * select closest Gateway
+ * @return the closest Gateway
+ */
public Gateway select() {
- GatewaySelector gateway_selector = new GatewaySelector(gateways);
- return gateway_selector.select();
+ GatewaySelector gatewaySelector = new GatewaySelector(gateways);
+ return gatewaySelector.select();
}
+ /**
+ * check if there are no gateways defined
+ * @return true if no gateways defined else false
+ */
public boolean isEmpty() {
return gateways.isEmpty();
}
+ /**
+ * @return number of gateways defined in the GatewaysManager
+ */
public int size() {
return gateways.size();
}
- public void addFromString(String gateways) {
- List<Gateway> gateways_list = new ArrayList<>();
- try {
- gateways_list = new Gson().fromJson(gateways, list_type);
- } catch (JsonSyntaxException e) {
- gateways_list.add(new Gson().fromJson(gateways, Gateway.class));
- }
-
- if (gateways_list != null) {
- for (Gateway gateway : gateways_list)
- addGateway(gateway);
- }
- }
-
@Override
public String toString() {
- return new Gson().toJson(gateways, list_type);
+ return new Gson().toJson(gateways, listType);
}
- public void fromEipServiceJson(JSONObject eipDefinition) {
+ /**
+ * parse gateways from eipDefinition
+ * @param eipDefinition eipServiceJson
+ */
+ void fromEipServiceJson(JSONObject eipDefinition) {
try {
JSONArray gatewaysDefined = eipDefinition.getJSONArray("gateways");
for (int i = 0; i < gatewaysDefined.length(); i++) {
@@ -113,6 +110,11 @@ public class GatewaysManager {
}
}
+ /**
+ * check if a gateway is an OpenVpn gateway
+ * @param gateway to check
+ * @return true if gateway is an OpenVpn gateway otherwise false
+ */
private boolean isOpenVpnGateway(JSONObject gateway) {
try {
String transport = gateway.getJSONObject("capabilities").getJSONArray("transport").toString();
@@ -137,7 +139,7 @@ public class GatewaysManager {
private boolean containsProfileWithSecrets(VpnProfile profile) {
boolean result = false;
- Collection<VpnProfile> profiles = profile_manager.getProfiles();
+ Collection<VpnProfile> profiles = profileManager.getProfiles();
for (VpnProfile aux : profiles) {
result = result || sameConnections(profile.mConnections, aux.mConnections)
&& profile.mClientCertFilename.equalsIgnoreCase(aux.mClientCertFilename)
@@ -146,11 +148,11 @@ public class GatewaysManager {
return result;
}
- protected void clearGatewaysAndProfiles() {
+ void clearGatewaysAndProfiles() {
gateways.clear();
- ArrayList<VpnProfile> profiles = new ArrayList<>(profile_manager.getProfiles());
+ ArrayList<VpnProfile> profiles = new ArrayList<>(profileManager.getProfiles());
for (VpnProfile profile : profiles) {
- profile_manager.removeProfile(context, profile);
+ profileManager.removeProfile(context, profile);
}
}
@@ -159,42 +161,62 @@ public class GatewaysManager {
gateways.add(gateway);
VpnProfile profile = gateway.getProfile();
- profile_manager.addProfile(profile);
+ profileManager.addProfile(profile);
}
private void removeDuplicatedGateway(Gateway gateway) {
Iterator<Gateway> it = gateways.iterator();
- List<Gateway> gateways_to_remove = new ArrayList<>();
+ List<Gateway> gatewaysToRemove = new ArrayList<>();
while (it.hasNext()) {
Gateway aux = it.next();
if (sameConnections(aux.getProfile().mConnections, gateway.getProfile().mConnections)) {
- gateways_to_remove.add(aux);
+ gatewaysToRemove.add(aux);
}
}
- gateways.removeAll(gateways_to_remove);
+ gateways.removeAll(gatewaysToRemove);
removeDuplicatedProfiles(gateway.getProfile());
}
private void removeDuplicatedProfiles(VpnProfile original) {
- Collection<VpnProfile> profiles = profile_manager.getProfiles();
- List<VpnProfile> remove_list = new ArrayList<>();
+ Collection<VpnProfile> profiles = profileManager.getProfiles();
+ List<VpnProfile> removeList = new ArrayList<>();
for (VpnProfile aux : profiles) {
- if (sameConnections(original.mConnections, aux.mConnections))
- remove_list.add(aux);
+ if (sameConnections(original.mConnections, aux.mConnections)) {
+ removeList.add(aux);
+ }
+ }
+ for (VpnProfile profile : removeList) {
+ profileManager.removeProfile(context, profile);
}
- for (VpnProfile profile : remove_list)
- profile_manager.removeProfile(context, profile);
}
+ /**
+ * check if all connections in c1 are also in c2
+ * @param c1 array of connections
+ * @param c2 array of connections
+ * @return true if all connections of c1 exist in c2 and vice versa
+ */
private boolean sameConnections(Connection[] c1, Connection[] c2) {
- int same_connections = 0;
+ int sameConnections = 0;
for (Connection c1_aux : c1) {
for (Connection c2_aux : c2)
if (c2_aux.mServerName.equals(c1_aux.mServerName)) {
- same_connections++;
+ sameConnections++;
break;
}
}
- return c1.length == c2.length && c1.length == same_connections;
+ return c1.length == c2.length && c1.length == sameConnections;
+ }
+
+ /**
+ * read EipServiceJson from preferences and set gateways
+ */
+ void configureFromPreferences() {
+ //TODO: THIS IS A QUICK FIX - it deletes all profiles in ProfileManager, thus it's possible
+ // to add all gateways from prefs without duplicates, but this should be refactored.
+ clearGatewaysAndProfiles();
+ fromEipServiceJson(
+ ConfigHelper.getEipDefinitionFromPreferences(preferences)
+ );
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/views/VpnStateImage.java b/app/src/main/java/se/leap/bitmaskclient/views/VpnStateImage.java
index 2efd83d6..86761642 100644
--- a/app/src/main/java/se/leap/bitmaskclient/views/VpnStateImage.java
+++ b/app/src/main/java/se/leap/bitmaskclient/views/VpnStateImage.java
@@ -1,3 +1,19 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
package se.leap.bitmaskclient.views;
import android.content.Context;
diff --git a/app/src/main/res/drawable/ic_menu_color_point.png b/app/src/main/res/drawable/ic_menu_color_point.png
new file mode 100644
index 00000000..ef4b0e51
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_color_point.png
Binary files differ
diff --git a/app/src/main/res/drawable/ic_menu_default.png b/app/src/main/res/drawable/ic_menu_default.png
new file mode 100644
index 00000000..e0d29163
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_default.png
Binary files differ
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 77bac436..968ac2e0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -114,4 +114,9 @@
<string name="do_not_show_again">Do not show again.</string>
<string name="always_on_vpn_user_message">To enable always-on VPN in Android VPN Settings click on the configure icon [img src] and turn the switch on."</string>
<string name="title_activity_provider_add">ProviderAddActivity</string>
+ <string name="donate_title">Donate</string>
+ <string name="donate_default_message">Please donate today if you value secure communication that is easy for both the end-user and the service provider.</string>
+ <string name="donate_message">LEAP depends on donations and grants. Please donate today if you value secure communication that is easy for both the end-user and the service provider.</string>
+ <string name="donate_button_remind_later">Remind me later</string>
+ <string name="donate_button_donate">Donate</string>
</resources>