diff options
author | Parménides GV <parmegv@sdf.org> | 2015-06-01 10:50:02 +0200 |
---|---|---|
committer | Parménides GV <parmegv@sdf.org> | 2015-06-01 10:50:02 +0200 |
commit | 21aa11e5e04ffef3111010140cd7336fe181de39 (patch) | |
tree | 6af11a281ce9fd4c8e70863d10093d910751bf66 /app/src/main | |
parent | e5e9ac6e43b9cdec0f362711bb33747ab73fc297 (diff) | |
parent | 03973cf7f9b0f8635b6835c548b192eb53a2be35 (diff) |
Merge branch 'feature/Look-for-a-better-solution-to-the-VPN-slider-#6863' into develop
Diffstat (limited to 'app/src/main')
32 files changed, 770 insertions, 538 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 4207872d..bdc36e89 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -29,11 +29,10 @@ import org.jetbrains.annotations.*; import org.json.*; import java.net.*; -import java.util.*; import butterknife.*; -import de.blinkt.openvpn.activities.*; import se.leap.bitmaskclient.eip.*; +import se.leap.bitmaskclient.userstatus.*; /** * The main user facing Activity of Bitmask Android, consisting of status, controls, @@ -42,7 +41,7 @@ import se.leap.bitmaskclient.eip.*; * @author Sean Leonard <meanderingcode@aetherislands.net> * @author parmegv */ -public class Dashboard extends Activity implements SessionDialog.SessionDialogInterface, ProviderAPIResultReceiver.Receiver, Observer { +public class Dashboard extends Activity implements ProviderAPIResultReceiver.Receiver { protected static final int CONFIGURE_LEAP = 0; protected static final int SWITCH_PROVIDER = 1; @@ -62,14 +61,10 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn @InjectView(R.id.providerName) TextView provider_name; - @InjectView(R.id.user_session_status) - TextView user_session_status_text_view; - @InjectView(R.id.user_session_status_progress) - ProgressBar user_session_status_progress_bar; - - EipFragment eip_fragment; - private Provider provider; - private UserSessionStatus user_session_status; + + VpnFragment eip_fragment; + UserStatusFragment user_status_fragment; + private static Provider provider = new Provider(); public ProviderAPIResultReceiver providerAPI_result_receiver; private boolean switching_provider; @@ -78,22 +73,23 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn super.onCreate(savedInstanceState); app = this; - user_session_status = UserSessionStatus.getInstance(getResources()); - user_session_status.addObserver(this); PRNGFixes.apply(); preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); fragment_manager = new FragmentManagerEnhanced(getFragmentManager()); handleVersion(); - User.init(); + User.init(getString(R.string.default_username)); + + ProviderAPICommand.initialize(this); + providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler(), this); restoreProvider(savedInstanceState); - if (provider == null || provider.getName().isEmpty()) + if (!provider.isConfigured()) startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP); else { buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false)); - restoreSessionStatus(savedInstanceState); + user_status_fragment.restoreSessionStatus(savedInstanceState); } } @@ -102,32 +98,20 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn if (savedInstanceState.containsKey(Provider.KEY)) provider = savedInstanceState.getParcelable(Provider.KEY); } - if (provider == null && preferences.getBoolean(Constants.PROVIDER_CONFIGURED, false)) + if (!provider.isConfigured() && preferences.getBoolean(Constants.PROVIDER_CONFIGURED, false)) provider = getSavedProviderFromSharedPreferences(); } - private void restoreSessionStatus(Bundle savedInstanceState) { - if (savedInstanceState != null) - if (savedInstanceState.containsKey(UserSessionStatus.TAG)) { - UserSessionStatus.SessionStatus status = (UserSessionStatus.SessionStatus) savedInstanceState.getSerializable(UserSessionStatus.TAG); - user_session_status.updateStatus(status, getResources()); - } - } - @Override protected void onSaveInstanceState(@NotNull Bundle outState) { - if (provider != null) - outState.putParcelable(Provider.KEY, provider); - if (user_session_status_text_view != null && user_session_status_text_view.getVisibility() == TextView.VISIBLE) - outState.putSerializable(UserSessionStatus.TAG, user_session_status.sessionStatus()); - + outState.putParcelable(Provider.KEY, provider); super.onSaveInstanceState(outState); } private Provider getSavedProviderFromSharedPreferences() { - Provider provider = null; + Provider provider = new Provider(); try { - provider = new Provider(new URL(preferences.getString(Provider.MAIN_URL, ""))); + provider.setUrl(new URL(preferences.getString(Provider.MAIN_URL, ""))); provider.define(new JSONObject(preferences.getString(Provider.KEY, ""))); } catch (MalformedURLException | JSONException e) { e.printStackTrace(); @@ -162,12 +146,12 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn providerToPreferences(provider); buildDashboard(false); - invalidateOptionsMenu(); + invalidateOptionsMenuOnUiThread(); if (data.hasExtra(SessionDialog.TAG)) { sessionDialog(Bundle.EMPTY); } - } else if (resultCode == RESULT_CANCELED && data.hasExtra(ACTION_QUIT)) { + } else if (resultCode == RESULT_CANCELED && data != null && data.hasExtra(ACTION_QUIT)) { finish(); } else configErrorDialog(); @@ -214,33 +198,35 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn ButterKnife.inject(this); provider_name.setText(provider.getDomain()); + + user_status_fragment = new UserStatusFragment(); + Bundle bundle = new Bundle(); + bundle.putBoolean(Provider.ALLOW_REGISTRATION, provider.allowsRegistration()); + user_status_fragment.setArguments(bundle); + fragment_manager.replace(R.id.user_status_fragment, user_status_fragment, UserStatusFragment.TAG); + if (provider.hasEIP()) { - fragment_manager.removePreviousFragment(EipFragment.TAG); - eip_fragment = new EipFragment(); + fragment_manager.removePreviousFragment(VpnFragment.TAG); + eip_fragment = new VpnFragment(); if (hide_and_turn_on_eip) { preferences.edit().remove(Dashboard.START_ON_BOOT).apply(); Bundle arguments = new Bundle(); - arguments.putBoolean(EipFragment.START_ON_BOOT, true); + arguments.putBoolean(VpnFragment.START_ON_BOOT, true); if (eip_fragment != null) eip_fragment.setArguments(arguments); } - fragment_manager.replace(R.id.servicesCollection, eip_fragment, EipFragment.TAG); + fragment_manager.replace(R.id.servicesCollection, eip_fragment, VpnFragment.TAG); if (hide_and_turn_on_eip) { onBackPressed(); } } - handleNewUserSessionStatus(user_session_status); } @Override public boolean onPrepareOptionsMenu(Menu menu) { if (provider.allowsRegistration()) { menu.findItem(R.id.signup_button).setVisible(true); - - boolean logged_in = User.loggedIn(); - menu.findItem(R.id.login_button).setVisible(!logged_in); - menu.findItem(R.id.logout_button).setVisible(logged_in); } return true; } @@ -262,15 +248,9 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn return true; case R.id.switch_provider: switching_provider = true; - if (LeapSRPSession.loggedIn()) logOut(); + if (User.loggedIn()) user_status_fragment.logOut(); else switchProvider(); return true; - case R.id.login_button: - sessionDialog(Bundle.EMPTY); - return true; - case R.id.logout_button: - logOut(); - return true; case R.id.signup_button: sessionDialog(Bundle.EMPTY); return true; @@ -285,131 +265,22 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn } public void showLog() { - Intent startLW = new Intent(getContext(), LogWindow.class); - startActivity(startLW); - } - - @Override - public void signUp(String username, String password) { - User.setUserName(username); - Bundle parameters = bundlePassword(password); - providerApiCommand(parameters, 0, ProviderAPI.SIGN_UP); - } - - @Override - public void logIn(String username, String password) { - User.setUserName(username); - Bundle parameters = bundlePassword(password); - providerApiCommand(parameters, 0, ProviderAPI.LOG_IN); - } - - public void logOut() { - providerApiCommand(Bundle.EMPTY, 0, ProviderAPI.LOG_OUT); - } - - @Override - public void update(Observable observable, Object data) { - if (observable instanceof UserSessionStatus) { - UserSessionStatus status = (UserSessionStatus) observable; - handleNewUserSessionStatus(status); - } - } - - private void handleNewUserSessionStatus(UserSessionStatus status) { - user_session_status = status; - if (provider.allowsRegistration()) { - if (user_session_status.inProgress()) - showUserSessionProgressBar(); - else - hideUserSessionProgressBar(); - changeSessionStatusMessage(user_session_status.toString()); - invalidateOptionsMenu(); - } - } - - private void changeSessionStatusMessage(final String message) { - runOnUiThread(new Runnable() { - @Override - public void run() { - user_session_status_text_view.setText(message); - } - }); - } - - private void showUserSessionProgressBar() { - runOnUiThread(new Runnable() { - @Override - public void run() { - user_session_status_progress_bar.setVisibility(ProgressBar.VISIBLE); - } - }); - } - - private void hideUserSessionProgressBar() { - runOnUiThread(new Runnable() { - @Override - public void run() { - user_session_status_progress_bar.setVisibility(ProgressBar.GONE); - } - }); + LogWindowWrapper log_window_wrapper = LogWindowWrapper.getInstance(getContext()); + log_window_wrapper.showLog(); } - protected void downloadVpnCertificate() { - boolean is_authenticated = LeapSRPSession.loggedIn(); + public void downloadVpnCertificate() { + boolean is_authenticated = User.loggedIn(); boolean allowed_anon = preferences.getBoolean(Constants.ALLOWED_ANON, false); if (allowed_anon || is_authenticated) - providerApiCommand(Bundle.EMPTY, R.string.downloading_certificate_message, ProviderAPI.DOWNLOAD_CERTIFICATE); + ProviderAPICommand.execute(Bundle.EMPTY, ProviderAPI.DOWNLOAD_CERTIFICATE, providerAPI_result_receiver); else sessionDialog(Bundle.EMPTY); - - } - - private Bundle bundlePassword(String password) { - Bundle parameters = new Bundle(); - if (!password.isEmpty()) - parameters.putString(SessionDialog.PASSWORD, password); - return parameters; - } - - protected void providerApiCommand(Bundle parameters, int progressbar_message_resId, String providerApi_action) { - if (eip_fragment != null && progressbar_message_resId != 0) { - eip_fragment.progress_bar.setVisibility(ProgressBar.VISIBLE); - setStatusMessage(progressbar_message_resId); - } - - Intent command = prepareProviderAPICommand(parameters, providerApi_action); - startService(command); - } - - private Intent prepareProviderAPICommand(Bundle parameters, String action) { - providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); - providerAPI_result_receiver.setReceiver(this); - - Intent command = new Intent(this, ProviderAPI.class); - - command.putExtra(ProviderAPI.PARAMETERS, parameters); - command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); - command.setAction(action); - return command; - } - - public void cancelLoginOrSignup() { - EipStatus.getInstance().setConnectedOrDisconnected(); } public void sessionDialog(Bundle resultData) { - FragmentTransaction transaction = fragment_manager.removePreviousFragment(SessionDialog.TAG); - - DialogFragment newFragment = new SessionDialog(); - if (provider.getName().equalsIgnoreCase("riseup")) { - resultData = resultData == Bundle.EMPTY ? new Bundle() : resultData; - resultData.putBoolean(SessionDialog.ERRORS.RISEUP_WARNING.toString(), true); - } - if (resultData != null && !resultData.isEmpty()) { - newFragment.setArguments(resultData); - } - newFragment.show(transaction, SessionDialog.TAG); + SessionDialog.getInstance(provider, resultData).show(transaction, SessionDialog.TAG); } private void switchProvider() { @@ -425,7 +296,7 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn if (resultCode == ProviderAPI.SUCCESSFUL_SIGNUP) { String username = resultData.getString(SessionDialog.USERNAME); String password = resultData.getString(SessionDialog.PASSWORD); - logIn(username, password); + user_status_fragment.logIn(username, password); } else if (resultCode == ProviderAPI.FAILED_SIGNUP) { sessionDialog(resultData); } else if (resultCode == ProviderAPI.SUCCESSFUL_LOGIN) { @@ -438,7 +309,6 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn setResult(RESULT_CANCELED); } else if (resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { eip_fragment.updateEipService(); - eip_fragment.handleNewVpnCertificate(); setResult(RESULT_OK); } else if (resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { setResult(RESULT_CANCELED); @@ -450,18 +320,24 @@ public class Dashboard extends Activity implements SessionDialog.SessionDialogIn } } - private void setStatusMessage(int string_resId) { - if (eip_fragment != null && eip_fragment.status_message != null) - eip_fragment.status_message.setText(string_resId); - } - public static Context getContext() { return app; } + public static Provider getProvider() { return provider; } + @Override public void startActivityForResult(Intent intent, int requestCode) { intent.putExtra(Dashboard.REQUEST_CODE, requestCode); super.startActivityForResult(intent, requestCode); } + + public void invalidateOptionsMenuOnUiThread() { + runOnUiThread(new Runnable() { + @Override + public void run() { + invalidateOptionsMenu(); + } + }); + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/DefaultedURL.java b/app/src/main/java/se/leap/bitmaskclient/DefaultedURL.java new file mode 100644 index 00000000..8daa7d8c --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/DefaultedURL.java @@ -0,0 +1,37 @@ +package se.leap.bitmaskclient; + +import java.net.*; + +public class DefaultedURL { + private URL DEFAULT_URL; + private String default_url = "https://example.net"; + + private URL url; + + public DefaultedURL() { + try { + DEFAULT_URL = new URL(default_url); + url = DEFAULT_URL; + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + + public boolean isDefault() { return url.equals(DEFAULT_URL); } + + public void setUrl(URL url) { + this.url = url; + } + + public String getDomain() { + return url.getHost(); + } + + public URL getUrl() { + return url; + } + + public String toString() { + return url.toString(); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java index 1ced6d60..49cf3774 100644 --- a/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java +++ b/app/src/main/java/se/leap/bitmaskclient/LeapSRPSession.java @@ -337,7 +337,7 @@ public class LeapSRPSession { return token; } - protected static boolean loggedIn() { + public static boolean loggedIn() { return !token.isEmpty(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/LogWindowWrapper.java b/app/src/main/java/se/leap/bitmaskclient/LogWindowWrapper.java new file mode 100644 index 00000000..2476f6a4 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/LogWindowWrapper.java @@ -0,0 +1,28 @@ +package se.leap.bitmaskclient; + +import android.content.*; + +import de.blinkt.openvpn.activities.*; + +public class LogWindowWrapper { + private static LogWindowWrapper instance; + + private static String TAG = LogWindowWrapper.class.getName(); + private Context context; + + public LogWindowWrapper(Context context) { + this.context = context; + } + + public void showLog() { + Intent startLW = new Intent(context, LogWindow.class); + startLW.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(startLW); + } + + public static LogWindowWrapper getInstance(Context context) { + if(instance == null) + instance = new LogWindowWrapper(context); + return instance; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java index a030927d..559b47d1 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -30,8 +30,8 @@ import java.util.*; */ public final class Provider implements Parcelable { - private JSONObject definition; // Represents our Provider's provider.json - private URL main_url; + private JSONObject definition = new JSONObject(); // Represents our Provider's provider.json + private DefaultedURL main_url = new DefaultedURL(); private String certificate_pin = ""; final public static String @@ -59,12 +59,14 @@ public final class Provider implements Parcelable { private static final String API_TERM_DEFAULT_LANGUAGE = "default_language"; protected static final String[] API_EIP_TYPES = {"openvpn"}; + public Provider() { } + public Provider(URL main_url) { - this.main_url = main_url; + this.main_url.setUrl(main_url); } public Provider(URL main_url, String certificate_pin) { - this.main_url = main_url; + this.main_url.setUrl(main_url); this.certificate_pin = certificate_pin; } @@ -81,7 +83,7 @@ public final class Provider implements Parcelable { private Provider(Parcel in) { try { - main_url = new URL(in.readString()); + main_url.setUrl(new URL(in.readString())); String definition_string = in.readString(); if (!definition_string.isEmpty()) definition = new JSONObject((definition_string)); @@ -90,6 +92,14 @@ public final class Provider implements Parcelable { } } + public boolean isConfigured() { + return !main_url.isDefault() && definition.length() > 0; + } + + protected void setUrl(URL url) { + main_url.setUrl(url); + } + protected void define(JSONObject provider_json) { definition = provider_json; } @@ -99,16 +109,16 @@ public final class Provider implements Parcelable { } protected String getDomain() { - return main_url.getHost(); + return main_url.getDomain(); } - protected URL mainUrl() { + protected DefaultedURL mainUrl() { return main_url; } protected String certificatePin() { return certificate_pin; } - protected String getName() { + public String getName() { // Should we pass the locale in, or query the system here? String lang = Locale.getDefault().getLanguage(); String name = ""; @@ -118,7 +128,7 @@ public final class Provider implements Parcelable { else throw new JSONException("Provider not defined"); } catch (JSONException e) { if (main_url != null) { - String host = main_url.getHost(); + String host = main_url.getDomain(); name = host.substring(0, host.indexOf(".")); } } @@ -181,7 +191,8 @@ public final class Provider implements Parcelable { @Override public void writeToParcel(Parcel parcel, int i) { - parcel.writeString(main_url.toString()); + if(main_url != null) + parcel.writeString(main_url.toString()); if (definition != null) parcel.writeString(definition.toString()); } @@ -190,7 +201,7 @@ public final class Provider implements Parcelable { public boolean equals(Object o) { if (o instanceof Provider) { Provider p = (Provider) o; - return p.mainUrl().getHost().equals(mainUrl().getHost()); + return p.mainUrl().getDomain().equals(mainUrl().getDomain()); } else return false; } @@ -206,6 +217,6 @@ public final class Provider implements Parcelable { @Override public int hashCode() { - return mainUrl().getHost().hashCode(); + return mainUrl().getDomain().hashCode(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderAPICommand.java b/app/src/main/java/se/leap/bitmaskclient/ProviderAPICommand.java new file mode 100644 index 00000000..0e4cfe8a --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderAPICommand.java @@ -0,0 +1,45 @@ +package se.leap.bitmaskclient; + +import android.content.*; +import android.os.*; + +import org.jetbrains.annotations.*; + +public class ProviderAPICommand { + private static Context context; + + private static String action; + private static Bundle parameters; + private static ResultReceiver result_receiver; + + public static void initialize(Context context) { + ProviderAPICommand.context = context; + } + + private static boolean isInitialized() { + return context != null; + } + + public static void execute(Bundle parameters, @NotNull String action, @NotNull ResultReceiver result_receiver) throws IllegalStateException { + if(!isInitialized()) throw new IllegalStateException(); + + ProviderAPICommand.action = action; + ProviderAPICommand.parameters = parameters; + ProviderAPICommand.result_receiver = result_receiver; + + Intent intent = setUpIntent(); + context.startService(intent); + } + + private static Intent setUpIntent() { + Intent command = new Intent(context, ProviderAPI.class); + + command.setAction(action); + command.putExtra(ProviderAPI.PARAMETERS, parameters); + command.putExtra(ProviderAPI.RECEIVER_KEY, result_receiver); + + return command; + } + + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderAPIResultReceiver.java b/app/src/main/java/se/leap/bitmaskclient/ProviderAPIResultReceiver.java index 533e5caf..9b880f89 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderAPIResultReceiver.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderAPIResultReceiver.java @@ -26,8 +26,9 @@ import android.os.*; public class ProviderAPIResultReceiver extends ResultReceiver {
private Receiver mReceiver;
- public ProviderAPIResultReceiver(Handler handler) {
+ public ProviderAPIResultReceiver(Handler handler, Receiver receiver) {
super(handler);
+ setReceiver(receiver);
// TODO Auto-generated constructor stub
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java index 220a71c8..13ef9b80 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java @@ -28,7 +28,8 @@ public class ProviderManager implements AdapteeCollection<Provider> { if (instance == null) instance = new ProviderManager(assets_manager); - instance.addCustomProviders(external_files_dir); + if(external_files_dir != null) + instance.addCustomProviders(external_files_dir); return instance; } @@ -120,7 +121,8 @@ public class ProviderManager implements AdapteeCollection<Provider> { public Set<Provider> providers() { Set<Provider> all_providers = new HashSet<Provider>(); all_providers.addAll(default_providers); - all_providers.addAll(custom_providers); + if(custom_providers != null) + all_providers.addAll(custom_providers); return all_providers; } @@ -140,25 +142,27 @@ public class ProviderManager implements AdapteeCollection<Provider> { } @Override - public void add(Provider element) { + public boolean add(Provider element) { if (!default_providers.contains(element)) - custom_providers.add(element); + return custom_providers.add(element); + else return true; } @Override - public void remove(Provider element) { - custom_providers.remove(element); + public boolean remove(Object element) { + return custom_providers.remove(element); } @Override - public void addAll(Collection<Provider> elements) { - custom_providers.addAll(elements); + public boolean addAll(Collection<? extends Provider> elements) { + return custom_providers.addAll(elements); } @Override - public void removeAll(Collection<Provider> elements) { - custom_providers.removeAll(elements); - default_providers.removeAll(elements); + public boolean removeAll(Collection<?> elements) { + if(!elements.getClass().equals(Provider.class)) + return false; + return default_providers.removeAll(elements) || custom_providers.removeAll(elements); } @Override diff --git a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java index bfa67815..2e3d7524 100644 --- a/app/src/main/java/se/leap/bitmaskclient/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java @@ -19,7 +19,6 @@ package se.leap.bitmaskclient; import android.app.*; import android.content.*; import android.os.*; -import android.util.*; import android.view.*; import android.widget.*; @@ -29,26 +28,24 @@ import java.util.*; import butterknife.*; import de.blinkt.openvpn.activities.*; +import mbanje.kurt.fabbutton.*; import se.leap.bitmaskclient.eip.*; -public class EipFragment extends Fragment implements Observer { +public class VpnFragment extends Fragment implements Observer { - public static String TAG = EipFragment.class.getSimpleName(); + public static String TAG = VpnFragment.class.getSimpleName(); - protected static final String IS_PENDING = TAG + ".is_pending"; + public static final String IS_PENDING = TAG + ".is_pending"; protected static final String IS_CONNECTED = TAG + ".is_connected"; - protected static final String STATUS_MESSAGE = TAG + ".status_message"; public static final String START_ON_BOOT = "start on boot"; - @InjectView(R.id.eipSwitch) - Switch eip_switch; - @InjectView(R.id.status_message) - TextView status_message; - @InjectView(R.id.eipProgress) - ProgressBar progress_bar; + @InjectView(R.id.vpn_Status_Image) + FabButton vpn_status_image; + @InjectView(R.id.vpn_main_button) + Button main_button; private static Dashboard dashboard; - private static EIPReceiver mEIPReceiver; + private static EIPReceiver eip_receiver; private static EipStatus eip_status; private boolean wants_to_connect; @@ -56,7 +53,10 @@ public class EipFragment extends Fragment implements Observer { super.onAttach(activity); dashboard = (Dashboard) activity; - dashboard.providerApiCommand(Bundle.EMPTY, 0, ProviderAPI.DOWNLOAD_EIP_SERVICE); + ProviderAPIResultReceiver provider_api_receiver = new ProviderAPIResultReceiver(new Handler(), dashboard); + + if(eip_receiver != null) + ProviderAPICommand.execute(Bundle.EMPTY, ProviderAPI.DOWNLOAD_EIP_SERVICE, provider_api_receiver); } @Override @@ -64,7 +64,7 @@ public class EipFragment extends Fragment implements Observer { super.onCreate(savedInstanceState); eip_status = EipStatus.getInstance(); eip_status.addObserver(this); - mEIPReceiver = new EIPReceiver(new Handler()); + eip_receiver = new EIPReceiver(new Handler()); } @Override @@ -72,9 +72,6 @@ public class EipFragment extends Fragment implements Observer { View view = inflater.inflate(R.layout.eip_service_fragment, container, false); ButterKnife.inject(this, view); - if (eip_status.isConnecting()) - eip_switch.setVisibility(View.VISIBLE); - Bundle arguments = getArguments(); if (arguments != null && arguments.containsKey(START_ON_BOOT) && arguments.getBoolean(START_ON_BOOT)) startEipFromScratch(); @@ -88,8 +85,6 @@ public class EipFragment extends Fragment implements Observer { eip_status.setConnecting(); else if (savedInstanceState.getBoolean(IS_CONNECTED)) eip_status.setConnectedOrDisconnected(); - else - status_message.setText(savedInstanceState.getString(STATUS_MESSAGE)); } @Override @@ -103,25 +98,20 @@ public class EipFragment extends Fragment implements Observer { public void onSaveInstanceState(Bundle outState) { outState.putBoolean(IS_PENDING, eip_status.isConnecting()); outState.putBoolean(IS_CONNECTED, eip_status.isConnected()); - outState.putString(STATUS_MESSAGE, status_message.getText().toString()); super.onSaveInstanceState(outState); } protected void saveStatus() { - boolean is_on = eip_switch.isChecked(); + boolean is_on = eip_status.isConnected() || eip_status.isConnecting(); Dashboard.preferences.edit().putBoolean(Dashboard.START_ON_BOOT, is_on).commit(); } - void handleNewVpnCertificate() { - handleSwitch(!eip_switch.isEnabled()); - } - - @OnCheckedChanged(R.id.eipSwitch) - void handleSwitch(boolean isChecked) { - if (isChecked) - handleSwitchOn(); - else + @OnClick(R.id.vpn_main_button) + void handleIcon() { + if (eip_status.isConnected() || eip_status.isConnecting()) handleSwitchOff(); + else + handleSwitchOn(); saveStatus(); } @@ -155,23 +145,22 @@ public class EipFragment extends Fragment implements Observer { } else if (eip_status.isConnected()) { askToStopEIP(); } else - setDisconnectedUI(); + updateIcon(); } private void askPendingStartCancellation() { AlertDialog.Builder alertBuilder = new AlertDialog.Builder(dashboard); alertBuilder.setTitle(dashboard.getString(R.string.eip_cancel_connect_title)) .setMessage(dashboard.getString(R.string.eip_cancel_connect_text)) - .setPositiveButton((R.string.yes), new DialogInterface.OnClickListener() { + .setPositiveButton((android.R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { askToStopEIP(); } }) - .setNegativeButton(dashboard.getString(R.string.no), new DialogInterface.OnClickListener() { + .setNegativeButton(dashboard.getString(android.R.string.no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - eip_switch.setChecked(true); } }) .show(); @@ -180,14 +169,7 @@ public class EipFragment extends Fragment implements Observer { public void startEipFromScratch() { wants_to_connect = false; eip_status.setConnecting(); - progress_bar.setVisibility(View.VISIBLE); - eip_switch.setVisibility(View.VISIBLE); - String status = dashboard.getString(R.string.eip_status_start_pending); - status_message.setText(status); - if (!eip_switch.isChecked()) { - eip_switch.setChecked(true); - } saveStatus(); eipCommand(Constants.ACTION_START_EIP); } @@ -205,12 +187,6 @@ public class EipFragment extends Fragment implements Observer { } protected void stopEipIfPossible() { - - hideProgressBar(); - - String message = dashboard.getString(R.string.eip_state_not_connected); - status_message.setText(message); - eipCommand(Constants.ACTION_STOP_EIP); } @@ -218,16 +194,15 @@ public class EipFragment extends Fragment implements Observer { AlertDialog.Builder alertBuilder = new AlertDialog.Builder(dashboard); alertBuilder.setTitle(dashboard.getString(R.string.eip_cancel_connect_title)) .setMessage(dashboard.getString(R.string.eip_warning_browser_inconsistency)) - .setPositiveButton((R.string.yes), new DialogInterface.OnClickListener() { + .setPositiveButton((android.R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { stopEipIfPossible(); } }) - .setNegativeButton(dashboard.getString(R.string.no), new DialogInterface.OnClickListener() { + .setNegativeButton(dashboard.getString(android.R.string.no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - eip_switch.setChecked(true); } }) .show(); @@ -247,7 +222,7 @@ public class EipFragment extends Fragment implements Observer { // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? Intent vpn_intent = new Intent(dashboard.getApplicationContext(), EIP.class); vpn_intent.setAction(action); - vpn_intent.putExtra(Constants.RECEIVER_TAG, mEIPReceiver); + vpn_intent.putExtra(Constants.RECEIVER_TAG, eip_receiver); dashboard.startService(vpn_intent); } @@ -266,69 +241,42 @@ public class EipFragment extends Fragment implements Observer { } private void handleNewState(EipStatus eip_status) { - if (eip_status.wantsToDisconnect()) - setDisconnectedUI(); - else if (eip_status.isConnecting()) - setInProgressUI(eip_status); - else if (eip_status.isConnected()) - setConnectedUI(); - else if (eip_status.isDisconnected() && !eip_status.isConnecting()) - setDisconnectedUI(); - } + Context context = dashboard.getApplicationContext(); + String error = eip_status.lastError(5, context); - private void setConnectedUI() { - hideProgressBar(); - adjustSwitch(); - status_message.setText(dashboard.getString(R.string.eip_state_connected)); - } - - private void setDisconnectedUI() { - hideProgressBar(); - adjustSwitch(); - if (eip_status.errorInLast(5, dashboard.getApplicationContext()) - && !status_message.getText().toString().equalsIgnoreCase(dashboard.getString(R.string.eip_state_not_connected))) { + if (!error.isEmpty()) { dashboard.showLog(); VoidVpnService.stop(); } - status_message.setText(dashboard.getString(R.string.eip_state_not_connected)); + updateIcon(); + updateButton(); } - private void adjustSwitch() { + private void updateIcon() { if (eip_status.isConnected() || eip_status.isConnecting()) { - if (!eip_switch.isChecked()) { - eip_switch.setChecked(true); + if(eip_status.isConnecting()) { + vpn_status_image.showProgress(true); + vpn_status_image.setIcon(R.drawable.ic_stat_vpn_empty_halo, R.drawable.ic_stat_vpn_empty_halo); + } else { + vpn_status_image.showProgress(false); + vpn_status_image.setIcon(R.drawable.ic_stat_vpn, R.drawable.ic_stat_vpn); } } else { - - if (eip_switch.isChecked()) { - eip_switch.setChecked(false); - } + vpn_status_image.setIcon(R.drawable.ic_stat_vpn_offline, R.drawable.ic_stat_vpn_offline); + vpn_status_image.showProgress(false); } } - private void setInProgressUI(EipStatus eip_status) { - int localizedResId = eip_status.getLocalizedResId(); - String logmessage = eip_status.getLogMessage(); - String prefix = dashboard.getString(localizedResId); - - showProgressBar(); - status_message.setText(prefix + " " + logmessage); - adjustSwitch(); - } - - private void updatingCertificateUI() { - showProgressBar(); - status_message.setText(getString(R.string.updating_certificate_message)); - } - - private void showProgressBar() { - if (progress_bar != null) - progress_bar.setVisibility(View.VISIBLE); - } - - private void hideProgressBar() { - if (progress_bar != null) - progress_bar.setVisibility(View.GONE); + private void updateButton() { + if (eip_status.isConnected() || eip_status.isConnecting()) { + if(eip_status.isConnecting()) { + main_button.setText(dashboard.getString(android.R.string.cancel)); + } else { + main_button.setText(dashboard.getString(R.string.vpn_button_turn_off)); + } + } else { + main_button.setText(dashboard.getString(R.string.vpn_button_turn_on)); + } } protected class EIPReceiver extends ResultReceiver { @@ -371,7 +319,6 @@ public class EipFragment extends Fragment implements Observer { case Activity.RESULT_OK: break; case Activity.RESULT_CANCELED: - updatingCertificateUI(); dashboard.downloadVpnCertificate(); break; } @@ -391,6 +338,6 @@ public class EipFragment extends Fragment implements Observer { public static EIPReceiver getReceiver() { - return mEIPReceiver; + return eip_receiver; } } 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 beed7948..9ff7f1af 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -96,7 +96,7 @@ public final class EIP extends IntentService { gateway = gateways_manager.select(); if (gateway != null && gateway.getProfile() != null) { - mReceiver = EipFragment.getReceiver(); + mReceiver = VpnFragment.getReceiver(); launchActiveGateway(); tellToReceiver(ACTION_START_EIP, Activity.RESULT_OK); } else 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 b7f95277..4bfef1cb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -31,7 +31,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { wants_to_disconnect = false, is_connecting = false; - + int last_error_line = 0; private String state, log_message; private int localized_res_id; @@ -137,18 +137,30 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { } public boolean errorInLast(int lines, Context context) { - boolean result = false; + return !lastError(lines, context).isEmpty(); + } + + public String lastError(int lines, Context context) { + String error = ""; + String[] error_keywords = {"error", "ERROR", "fatal", "FATAL"}; VpnStatus.LogItem[] log = VpnStatus.getlogbuffer(); + if(log.length < last_error_line) + last_error_line = 0; String message = ""; for (int i = 1; i <= lines && log.length > i; i++) { - message = log[log.length - i].getString(context); + int line = log.length - i; + VpnStatus.LogItem log_item = log[line]; + message = log_item.getString(context); for (int j = 0; j < error_keywords.length; j++) - if (message.contains(error_keywords[j])) - result = true; + if (message.contains(error_keywords[j]) && line > last_error_line) { + error = message; + last_error_line = line; + } } - return result; + + return error; } @Override 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 1c64328e..f41049c5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -16,19 +16,27 @@ */ package se.leap.bitmaskclient.eip; -import android.content.*; +import android.content.Context; +import android.content.SharedPreferences; -import com.google.gson.*; -import com.google.gson.reflect.*; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; -import org.json.*; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; -import java.lang.reflect.*; -import java.util.*; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; -import de.blinkt.openvpn.*; -import de.blinkt.openvpn.core.*; -import se.leap.bitmaskclient.*; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.Connection; +import de.blinkt.openvpn.core.ProfileManager; +import se.leap.bitmaskclient.Provider; /** * @author parmegv @@ -65,7 +73,7 @@ public class GatewaysManager { } public void addFromString(String gateways) { - List<Gateway> gateways_list = new ArrayList<Gateway>(); + List<Gateway> gateways_list = new ArrayList<>(); try { gateways_list = new Gson().fromJson(gateways, list_type); } catch (JsonSyntaxException e) { @@ -75,7 +83,6 @@ public class GatewaysManager { if (gateways_list != null) { for (Gateway gateway : gateways_list) addGateway(gateway); - this.gateways.addAll(gateways_list); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java index dac92fe2..cbf0fed2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java @@ -50,6 +50,10 @@ public class VoidVpnService extends VpnService { closeFd(); } + public static boolean isRunning() throws NullPointerException { + return thread.isAlive() && fd != null; + } + private static void closeFd() { try { if (fd != null) diff --git a/app/src/main/java/se/leap/bitmaskclient/userstatus/FabButton.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/FabButton.java new file mode 100644 index 00000000..1bf1847c --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/FabButton.java @@ -0,0 +1,34 @@ +package se.leap.bitmaskclient.userstatus; + + +import android.content.Context; +import android.util.AttributeSet; + +import mbanje.kurt.fabbutton.CircleImageView; +import se.leap.bitmaskclient.R; + +public class FabButton extends mbanje.kurt.fabbutton.FabButton { + + + public FabButton(Context context) { + super(context); + } + + public FabButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FabButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void init(Context context, AttributeSet attrs, int defStyle) { + super.init(context, attrs, defStyle); + super.init(context, attrs, defStyle); + } + + private CircleImageView getImage() { + return (CircleImageView) findViewById(R.id.fabbutton_circle); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/SessionDialog.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java index 30344ff2..7dbbe059 100644 --- a/app/src/main/java/se/leap/bitmaskclient/SessionDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java @@ -14,7 +14,7 @@ * 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; +package se.leap.bitmaskclient.userstatus; import android.app.*; import android.content.*; @@ -23,6 +23,9 @@ import android.view.*; import android.widget.*; import butterknife.*; +import se.leap.bitmaskclient.VpnFragment; +import se.leap.bitmaskclient.Provider; +import se.leap.bitmaskclient.R; /** * Implements the log in dialog, currently without progress dialog. @@ -56,8 +59,18 @@ public class SessionDialog extends DialogFragment { private static boolean is_eip_pending = false; - public SessionDialog() { - setArguments(Bundle.EMPTY); + public static SessionDialog getInstance(Provider provider, Bundle arguments) { + SessionDialog dialog = new SessionDialog(); + if (provider.getName().equalsIgnoreCase("riseup")) { + arguments = + arguments == Bundle.EMPTY ? + new Bundle() : arguments; + arguments.putBoolean(SessionDialog.ERRORS.RISEUP_WARNING.toString(), true); + } + if (arguments != null && !arguments.isEmpty()) { + dialog.setArguments(arguments); + } + return dialog; } public AlertDialog onCreateDialog(Bundle savedInstanceState) { @@ -68,7 +81,7 @@ public class SessionDialog extends DialogFragment { ButterKnife.inject(this, view); Bundle arguments = getArguments(); - if (arguments != Bundle.EMPTY) { + if (arguments != Bundle.EMPTY && arguments != null) { setUp(arguments); } @@ -100,7 +113,7 @@ public class SessionDialog extends DialogFragment { } private void setUp(Bundle arguments) { - is_eip_pending = arguments.getBoolean(EipFragment.IS_PENDING, false); + is_eip_pending = arguments.getBoolean(VpnFragment.IS_PENDING, false); if (arguments.containsKey(ERRORS.PASSWORD_INVALID_LENGTH.toString())) password_field.setError(getString(R.string.error_not_valid_password_user_message)); else if (arguments.containsKey(ERRORS.RISEUP_WARNING.toString())) { @@ -152,8 +165,9 @@ public class SessionDialog extends DialogFragment { @Override public void onAttach(Activity activity) { super.onAttach(activity); + try { - interface_with_Dashboard = (SessionDialogInterface) activity; + interface_with_Dashboard = (SessionDialogInterface) activity.getFragmentManager().findFragmentById(R.id.user_status_fragment);; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement LogInDialogListener"); diff --git a/app/src/main/java/se/leap/bitmaskclient/User.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/User.java index f3d3b8b1..64ce0629 100644 --- a/app/src/main/java/se/leap/bitmaskclient/User.java +++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/User.java @@ -14,16 +14,18 @@ * 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; +package se.leap.bitmaskclient.userstatus; + +import se.leap.bitmaskclient.LeapSRPSession; public class User { - private static String user_name = ""; + private static String user_name; private static User user; - public static int DEFAULT_CONJUGATION_PERSON = 1; - public static User init() { + public static User init(String default_username) { if (user == null) { user = new User(); + user.setUserName(default_username); } return user; } diff --git a/app/src/main/java/se/leap/bitmaskclient/UserSessionStatus.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatus.java index 28c7ecc7..edfed8d6 100644 --- a/app/src/main/java/se/leap/bitmaskclient/UserSessionStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatus.java @@ -14,15 +14,17 @@ * 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; +package se.leap.bitmaskclient.userstatus; import android.content.res.*; import java.util.*; -public class UserSessionStatus extends Observable { - public static String TAG = UserSessionStatus.class.getSimpleName(); - private static UserSessionStatus current_status; +import se.leap.bitmaskclient.R; + +public class UserStatus extends Observable { + public static String TAG = UserStatus.class.getSimpleName(); + private static UserStatus current_status; private static Resources resources; public enum SessionStatus { @@ -56,17 +58,17 @@ public class UserSessionStatus extends Observable { } } - private static SessionStatus session_status = SessionStatus.NOT_LOGGED_IN; + private static SessionStatus session_status = SessionStatus.LOGGED_OUT; - public static UserSessionStatus getInstance(Resources resources) { + public static UserStatus getInstance(Resources resources) { if (current_status == null) { - current_status = new UserSessionStatus(resources); + current_status = new UserStatus(resources); } return current_status; } - private UserSessionStatus(Resources resources) { - UserSessionStatus.resources = resources; + private UserStatus(Resources resources) { + UserStatus.resources = resources; } private void sessionStatus(SessionStatus session_status) { @@ -82,6 +84,14 @@ public class UserSessionStatus extends Observable { || session_status == SessionStatus.LOGGING_OUT; } + public boolean isLoggedIn() { + return session_status == SessionStatus.LOGGED_IN; + } + + public boolean isLoggedOut() { + return session_status == SessionStatus.LOGGED_OUT; + } + public static void updateStatus(SessionStatus session_status, Resources resources) { current_status = getInstance(resources); current_status.sessionStatus(session_status); @@ -93,7 +103,7 @@ public class UserSessionStatus extends Observable { public String toString() { String user_session_status = User.userName(); - String default_username = resources.getString(R.string.default_user, ""); + String default_username = resources.getString(R.string.default_username, ""); if(user_session_status.isEmpty() && !default_username.equalsIgnoreCase("null")) user_session_status = default_username; user_session_status += " " + session_status.toString(); diff --git a/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java new file mode 100644 index 00000000..f670553f --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java @@ -0,0 +1,181 @@ +package se.leap.bitmaskclient.userstatus; + +import android.app.Activity; +import android.app.Fragment; +import android.os.Bundle; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import org.jetbrains.annotations.NotNull; + +import java.util.Observable; +import java.util.Observer; + +import butterknife.ButterKnife; +import butterknife.InjectView; +import butterknife.OnClick; +import se.leap.bitmaskclient.Dashboard; +import se.leap.bitmaskclient.Provider; +import se.leap.bitmaskclient.ProviderAPI; +import se.leap.bitmaskclient.ProviderAPICommand; +import se.leap.bitmaskclient.ProviderAPIResultReceiver; +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.eip.EipStatus; + +public class UserStatusFragment extends Fragment implements Observer, SessionDialog.SessionDialogInterface { + + public static String TAG = UserStatusFragment.class.getSimpleName(); + private static Dashboard dashboard; + private ProviderAPIResultReceiver providerAPI_result_receiver; + + @InjectView(R.id.user_status_username) + TextView username; + @InjectView(R.id.user_status_icon) + FabButton icon; + @InjectView(R.id.user_status_button) + Button button; + + private UserStatus status; + private boolean allows_registration = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + status = UserStatus.getInstance(getResources()); + status.addObserver(this); + } + + @Override + public void onSaveInstanceState(@NotNull Bundle outState) { + if (username != null && username.getVisibility() == TextView.VISIBLE) + outState.putSerializable(UserStatus.TAG, status.sessionStatus()); + + super.onSaveInstanceState(outState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.user_session_fragment, container, false); + ButterKnife.inject(this, view); + + Bundle arguments = getArguments(); + allows_registration = arguments.getBoolean(Provider.ALLOW_REGISTRATION); + handleNewStatus(status); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + dashboard = (Dashboard) activity; + + providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler(), dashboard); + } + + public void restoreSessionStatus(Bundle savedInstanceState) { + if (savedInstanceState != null) + if (savedInstanceState.containsKey(UserStatus.TAG)) { + UserStatus.SessionStatus status = (UserStatus.SessionStatus) savedInstanceState.getSerializable(UserStatus.TAG); + this.status.updateStatus(status, getResources()); + } + } + + @OnClick(R.id.user_status_button) + public void handleButton() { + android.util.Log.d(TAG, status.toString()); + if(status.isLoggedIn()) + logOut(); + else if(status.isLoggedOut()) + dashboard.sessionDialog(Bundle.EMPTY); + else if(status.inProgress()) + cancelLoginOrSignup(); + } + + @Override + public void update(Observable observable, Object data) { + if (observable instanceof UserStatus) { + final UserStatus status = (UserStatus) observable; + dashboard.runOnUiThread(new Runnable() { + @Override + public void run() { + handleNewStatus(status); + } + }); + } + } + + private void handleNewStatus(UserStatus status) { + this.status = status; + if (allows_registration) { + if (this.status.inProgress()) + showUserSessionProgressBar(); + else + hideUserSessionProgressBar(); + changeMessage(); + updateButton(); + } + } + + private void showUserSessionProgressBar() { + icon.showProgress(true); + } + + private void hideUserSessionProgressBar() { + icon.showProgress(false); + } + + private void changeMessage() { + final String message = User.userName(); + username.setText(message); + } + + private void updateButton() { + if(status.isLoggedIn()) + button.setText(dashboard.getString(R.string.logout_button)); + else if(allows_registration) { + if (status.isLoggedOut()) + button.setText(dashboard.getString(R.string.login_button)); + else if (status.inProgress()) + button.setText(dashboard.getString(android.R.string.cancel)); + } + } + + + @Override + public void signUp(String username, String password) { + User.setUserName(username); + Bundle parameters = bundlePassword(password); + ProviderAPICommand.execute(parameters, ProviderAPI.SIGN_UP, providerAPI_result_receiver); + } + + @Override + public void logIn(String username, String password) { + User.setUserName(username); + Bundle parameters = bundlePassword(password); + ProviderAPICommand.execute(parameters, ProviderAPI.LOG_IN, providerAPI_result_receiver); + } + + public void logOut() { + android.util.Log.d(TAG, "Log out"); + ProviderAPICommand.execute(Bundle.EMPTY, ProviderAPI.LOG_OUT, providerAPI_result_receiver); + } + + public void cancelLoginOrSignup() { + EipStatus.getInstance().setConnectedOrDisconnected(); + } + + private Bundle bundlePassword(String password) { + Bundle parameters = new Bundle(); + if (!password.isEmpty()) + parameters.putString(SessionDialog.PASSWORD, password); + return parameters; + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_account_circle.png b/app/src/main/res/drawable-hdpi/ic_account_circle.png Binary files differnew file mode 100644 index 00000000..60e50a03 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_account_circle.png diff --git a/app/src/main/res/drawable-mdpi/ic_account_circle.png b/app/src/main/res/drawable-mdpi/ic_account_circle.png Binary files differnew file mode 100644 index 00000000..9cc9f0b5 --- /dev/null +++ b/app/src/main/res/drawable-mdpi/ic_account_circle.png diff --git a/app/src/main/res/drawable-xhdpi/ic_account_circle.png b/app/src/main/res/drawable-xhdpi/ic_account_circle.png Binary files differnew file mode 100644 index 00000000..8af55604 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_account_circle.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_account_circle.png b/app/src/main/res/drawable-xxhdpi/ic_account_circle.png Binary files differnew file mode 100644 index 00000000..6e81a76b --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/ic_account_circle.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_account_circle.png b/app/src/main/res/drawable-xxxhdpi/ic_account_circle.png Binary files differnew file mode 100644 index 00000000..9511ae3a --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/ic_account_circle.png diff --git a/app/src/main/res/layout-xlarge/dashboard.xml b/app/src/main/res/layout-xlarge/dashboard.xml index 268bd3f9..3c93a04c 100644 --- a/app/src/main/res/layout-xlarge/dashboard.xml +++ b/app/src/main/res/layout-xlarge/dashboard.xml @@ -5,80 +5,45 @@ android:layout_height="match_parent" android:orientation="vertical" android:layout_marginLeft="10sp" + android:layout_marginStart="10sp" android:layout_marginTop="10sp" tools:context=".Dashboard" > - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="42dp" - android:background="?android:attr/selectableItemBackground" > - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingLeft="10dp" > - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:singleLine="true" - android:text="@string/provider_label" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="32sp" /> - - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingLeft="32dp" > - <TextView - android:id="@+id/providerName" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:singleLine="true" - android:text="@string/provider_label_none" - android:textAppearance="?android:attr/textAppearanceMedium" /> - - </LinearLayout> - </LinearLayout> + <TextView + android:id="@+id/providerName" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="32dp" + android:layout_marginStart="32dp" + android:textSize="48sp" + android:ellipsize="marquee" + android:gravity="center_vertical" + android:text="@string/provider_label_none" + android:textAppearance="?android:attr/textAppearanceMedium" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingLeft="32dp"> - <ProgressBar - android:id="@+id/user_session_status_progress" - android:layout_width="wrap_content" - android:layout_height="fill_parent" - android:indeterminate="true" - android:visibility="gone"/> - <TextView - android:id="@+id/user_session_status" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="18sp" - android:layout_marginLeft="10dp" - android:ellipsize="marquee" - android:singleLine="true" - android:textAppearance="?android:attr/textAppearanceMedium" - /> + android:layout_marginLeft="32dp" + android:layout_marginStart="32dp"> + <ProgressBar + android:id="@+id/user_session_status_progress" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:indeterminate="true" + android:visibility="gone"/> + <TextView + android:id="@+id/user_session_status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="18sp" + android:layout_marginLeft="10dp" + android:layout_marginStart="10dp" + android:ellipsize="marquee" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceMedium" + /> </LinearLayout> - - <View - android:layout_width="wrap_content" - android:layout_height="1dp" - android:layout_marginBottom="7dp" - android:background="@android:drawable/divider_horizontal_bright" /> <LinearLayout android:id="@+id/servicesCollection" diff --git a/app/src/main/res/layout-xlarge/eip_service_fragment.xml b/app/src/main/res/layout-xlarge/eip_service_fragment.xml index 38b6aca3..a9f01fb8 100644 --- a/app/src/main/res/layout-xlarge/eip_service_fragment.xml +++ b/app/src/main/res/layout-xlarge/eip_service_fragment.xml @@ -3,54 +3,53 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" - android:layout_marginLeft="20dp" > + android:layout_marginLeft="20dp" + android:layout_marginStart="20dp"> <TextView android:id="@+id/eipLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" android:layout_marginLeft="10dp" - android:clickable="true" + android:layout_marginStart="10dp" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" android:text="@string/eip_service_label" android:textAppearance="?android:attr/textAppearanceLarge" - android:textSize="26sp" /> + android:textSize="24sp"/> - <Switch - android:id="@+id/eipSwitch" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_alignParentTop="true" - android:layout_marginRight="10dp" - android:height="26dp"/> - - <ProgressBar - android:id="@+id/eipProgress" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@android:style/Widget.Holo.ProgressBar.Horizontal" - android:indeterminate="true" - android:visibility="gone" - android:layout_below="@id/eipLabel" - android:layout_marginLeft="15dp" - android:layout_marginRight="15dp" /> - <TextView - android:id="@+id/status_message" + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:orientation="horizontal" android:layout_below="@+id/eipLabel" - android:paddingBottom="10dp" - android:paddingLeft="10dp" - android:paddingRight="10dp" - android:paddingTop="10dp" - android:layout_alignParentLeft="true" - android:layout_centerVertical="true" - android:clickable="true" - android:text="@string/eip_state_not_connected" - android:textSize="16sp" /> + android:layout_centerInParent="true"> + <Button + android:id="@+id/vpn.main.button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="24dp" + android:layout_marginLeft="24dp" + android:textSize="32sp" + /> + <view + android:id="@+id/vpn.Status.Image" + android:layout_width="48dp" + android:layout_height="48dp" + android:color="@android:color/holo_blue_dark" + class="mbanje.kurt.fabbutton.FabButton" + android:layout_gravity="center" + android:visibility="visible" + android:indeterminate="true" + android:max="100" + fbb_autoStart="true" + fbb_progressColor="#ff170aff" + fbb_progressWidthRatio="0.1" + android:layout_marginStart="24dp" + android:layout_marginLeft="24dp" + /> + </LinearLayout> </RelativeLayout> diff --git a/app/src/main/res/layout-xlarge/user_session_fragment.xml b/app/src/main/res/layout-xlarge/user_session_fragment.xml new file mode 100644 index 00000000..30969219 --- /dev/null +++ b/app/src/main/res/layout-xlarge/user_session_fragment.xml @@ -0,0 +1,55 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="30dp" + android:layout_marginLeft="60dp" + android:layout_marginStart="60dp" + android:orientation="vertical" + tools:context="se.leap.bitmaskclient.userstatus.UserStatusFragment"> + + <TextView + android:id="@+id/user.status.username" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="30dp" + android:layout_marginRight="60dp" + android:layout_marginEnd="60dp" + android:layout_gravity="center" + android:textSize="46sp" + android:text="@string/default_username" + android:textAppearance="?android:attr/textAppearanceMedium"/> + + <LinearLayout + android:id="@+id/user.status.buttonAndIcon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"> + + <Button + android:id="@+id/user.status.button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="24sp" + /> + + <view + android:id="@+id/user.status.icon" + android:layout_width="56dp" + android:layout_height="56dp" + android:layout_marginLeft="26dp" + android:layout_marginStart="26dp" + android:background="@android:color/transparent" + android:color="@android:color/holo_blue_dark" + class="mbanje.kurt.fabbutton.FabButton" + android:layout_gravity="center" + android:visibility="visible" + android:indeterminate="true" + android:max="100" + fbb_autoStart="true" + fbb_progressColor="#ff170aff" + fbb_progressWidthRatio="0.3" + /> + + </LinearLayout> +</LinearLayout> diff --git a/app/src/main/res/layout/dashboard.xml b/app/src/main/res/layout/dashboard.xml index f4269fe2..6a9bbe97 100644 --- a/app/src/main/res/layout/dashboard.xml +++ b/app/src/main/res/layout/dashboard.xml @@ -5,61 +5,30 @@ android:layout_height="match_parent" android:orientation="vertical" tools:context=".Dashboard" > - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="40dp" - android:background="?android:attr/selectableItemBackground" > - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="5dp" - android:ellipsize="marquee" - android:singleLine="true" - android:text="@string/provider_label" - android:textAppearance="?android:attr/textAppearanceMedium" /> - - <TextView - android:id="@+id/providerName" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="28sp" - android:layout_marginLeft="10dp" - android:ellipsize="marquee" - android:singleLine="true" - android:text="@string/provider_label_none" - android:textAppearance="?android:attr/textAppearanceMedium" /> - </LinearLayout> + <TextView + android:id="@+id/providerName" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="10dp" + android:layout_marginStart="10dp" + android:textSize="26sp" + android:ellipsize="marquee" + android:gravity="center_vertical" + android:singleLine="true" + android:text="@string/provider_label_none" + android:textAppearance="?android:attr/textAppearanceMedium" /> <LinearLayout + android:id="@+id/user.status.fragment" android:layout_width="match_parent" - android:layout_height="wrap_content"> - <ProgressBar - android:id="@+id/user_session_status_progress" - android:layout_width="wrap_content" - android:layout_height="fill_parent" - android:indeterminate="true" - android:visibility="gone"/> - <TextView - android:id="@+id/user_session_status" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="18sp" - android:layout_marginLeft="10dp" - android:ellipsize="marquee" - android:singleLine="true" - android:textAppearance="?android:attr/textAppearanceMedium" - /> - </LinearLayout> - + android:layout_height="wrap_content" + android:orientation="horizontal"/> + <LinearLayout android:id="@+id/servicesCollection" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="0.11" - android:orientation="vertical" > - </LinearLayout> - + android:orientation="vertical" /> </LinearLayout> diff --git a/app/src/main/res/layout/eip_service_fragment.xml b/app/src/main/res/layout/eip_service_fragment.xml index 6adcfab0..06b514d3 100644 --- a/app/src/main/res/layout/eip_service_fragment.xml +++ b/app/src/main/res/layout/eip_service_fragment.xml @@ -2,50 +2,49 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="10dp" > + android:layout_marginTop="10dp"> <TextView android:id="@+id/eipLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" android:layout_marginLeft="10dp" - android:clickable="true" + android:layout_marginStart="10dp" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" android:text="@string/eip_service_label" android:textAppearance="?android:attr/textAppearanceMedium" /> - <ProgressBar - android:id="@+id/eipProgress" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@android:style/Widget.Holo.ProgressBar.Horizontal" - android:indeterminate="true" - android:visibility="gone" - android:layout_below="@id/eipLabel" - android:layout_marginLeft="15dp" - android:layout_marginRight="15dp" /> - - <TextView - android:id="@+id/status_message" + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_below="@id/eipProgress" - android:layout_centerVertical="true" - android:paddingTop="5dp" - android:paddingBottom="10dp" - android:paddingLeft="10dp" - android:paddingRight="10dp" - android:clickable="true" - android:text="@string/eip_state_not_connected" /> + android:orientation="horizontal" + android:layout_below="@+id/eipLabel" + android:layout_centerInParent="true"> - <Switch - android:id="@+id/eipSwitch" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignTop="@+id/status_message" - android:layout_toRightOf="@+id/status_message" - android:layout_toEndOf="@+id/status_message" /> + <Button + android:id="@+id/vpn.main.button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="12sp" + /> + <view + android:id="@+id/vpn.Status.Image" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_marginLeft="3dp" + android:layout_marginStart="3dp" + android:color="@android:color/holo_blue_dark" + class="mbanje.kurt.fabbutton.FabButton" + android:layout_gravity="center" + android:visibility="visible" + android:indeterminate="true" + android:max="100" + fbb_autoStart="true" + fbb_progressColor="#ff170aff" + fbb_progressWidthRatio="0.1" + /> + </LinearLayout> </RelativeLayout> diff --git a/app/src/main/res/layout/user_session_fragment.xml b/app/src/main/res/layout/user_session_fragment.xml new file mode 100644 index 00000000..30068381 --- /dev/null +++ b/app/src/main/res/layout/user_session_fragment.xml @@ -0,0 +1,43 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:layout_marginLeft="20dp" + android:layout_marginStart="20dp" + tools:context="se.leap.bitmaskclient.userstatus.UserStatusFragment"> + + <TextView + android:id="@+id/user.status.username" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:layout_marginRight="15dp" + android:layout_marginEnd="20dp" + android:textSize="20sp" + android:text="@string/default_username" + android:textAppearance="?android:attr/textAppearanceMedium"/> + + <Button + android:id="@+id/user.status.button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + /> + + <view + android:id="@+id/user.status.icon" + android:layout_width="50dp" + android:layout_height="50dp" + android:layout_marginLeft="9dp" + android:layout_marginStart="9dp" + android:shadowRadius="0" + android:src="@drawable/ic_account_circle" + android:color="@android:color/transparent" + class="se.leap.bitmaskclient.userstatus.FabButton" + android:indeterminate="true" + fbb_progressColor="@android:color/holo_blue_dark" + fbb_progressWidthRatio="0.1" + /> + +</LinearLayout> diff --git a/app/src/main/res/menu/client_dashboard.xml b/app/src/main/res/menu/client_dashboard.xml index 9bc39d7b..e0336cc0 100644 --- a/app/src/main/res/menu/client_dashboard.xml +++ b/app/src/main/res/menu/client_dashboard.xml @@ -15,15 +15,5 @@ android:id="@+id/signup_button" android:title="@string/signup_button" android:visible="false"/> - <item - android:id="@+id/login_button" - android:showAsAction="ifRoom" - android:title="@string/login_button" - android:visible="false"/> - <item - android:id="@+id/logout_button" - android:showAsAction="ifRoom" - android:title="@string/logout_button" - android:visible="false"/> </menu> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 82ca44e9..7f0670b8 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -65,13 +65,11 @@ <string name="eip_cancel_connect_title">¿Abortar conexión?</string> <string name="eip_cancel_connect_text">Hay una conexión iniciándose. ¿Quieres cancelarla?</string> <string name="eip.warning.browser_inconsistency">Para salvaguardar la privacidad de tu información personal, te recomendamos que después de apagar la VPN cierres la sesión del navegador y abras una sesión privada en él. Gracias.</string> - <string name="yes">Sí</string> - <string name="no">No</string> <string name="eip_state_not_connected">"¡Conexión no protegida!"</string> <string name="eip_state_connected">Conexión protegida.</string> <string name="provider_problem">Parece que hay un problema con el proveedor.</string> <string name="try_another_provider">Prueba con otro proveedor, o contacta con este.</string> - <string name="default_user"></string> + <string name="default_username">Anónimo</string> <string name="logged_in_user_status">inició sesión.</string> <string name="logged_out_user_status">cerró la sesión.</string> <string name="didnt_log_out_user_status">no cerró la sesión.</string> @@ -79,5 +77,6 @@ <string name="logging_in_user_status">está iniciando sesión.</string> <string name="logging_out_user_status">está cerrando sesión.</string> <string name="signingup_message">está siendo registrado.</string> - + <string name="vpn.button.turn.on">Iniciar</string> + <string name="vpn.button.turn.off">Apagar</string> </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bcfd3a2c..7746795e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,13 +63,11 @@ <string name="eip_cancel_connect_title">Cancel connection?</string> <string name="eip_cancel_connect_text">There is a connection attempt in progress. Do you wish to cancel it?</string> <string name="eip.warning.browser_inconsistency">In order to avoid leaking your personal information, please close your browser and start a private window after disconnecting the Encrypted VPN Internet Access. Thanks.</string> - <string name="yes">Yes</string> - <string name="no">No</string> <string name="eip_state_not_connected">"Not running! Connection not secure!"</string> <string name="eip_state_connected">Connection Secure.</string> <string name="provider_problem">It seems there is a problem with the provider.</string> <string name="try_another_provider">Please try another provider, or contact yours.</string> - <string name="default_user">You</string> + <string name="default_username">Anonymous</string> <string name="logged_in_user_status">is logged in.</string> <string name="logged_out_user_status">logged out.</string> <string name="didnt_log_out_user_status">didn\'t log out. Try later, it may be a problem in the network or in the provider. If the problem persists, then wipe Bitmask data from the Android settings</string> @@ -77,4 +75,6 @@ <string name="logging_in_user_status">is logging in.</string> <string name="logging_out_user_status">is logging out.</string> <string name="signingup_message">is being registered.</string> + <string name="vpn.button.turn.on">Turn on</string> + <string name="vpn.button.turn.off">Turn off</string> </resources> |