summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2021-03-22 18:49:19 +0100
committercyberta <cyberta@riseup.net>2021-04-22 22:53:01 +0200
commitff5188d7de1df36c5d71d309a08d290560e9d337 (patch)
treed4f3aa7ccfc8a41ad38a6c9a21413aa33f49f537 /app/src/main/java/se/leap
parent95174abcb87af9d07465cda5c23cc35e1987b6d9 (diff)
initial draft for gateway selection on Android
Diffstat (limited to 'app/src/main/java/se/leap')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java286
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java24
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Location.java42
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java9
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java27
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java22
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java41
7 files changed, 447 insertions, 4 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java
new file mode 100644
index 00000000..b14a9e44
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java
@@ -0,0 +1,286 @@
+/**
+ * Copyright (c) 2021 LEAP Encryption Access Project and contributors
+ *
+ * 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.base.fragments;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.AppCompatButton;
+import androidx.appcompat.widget.AppCompatImageView;
+import androidx.appcompat.widget.AppCompatTextView;
+import androidx.core.content.ContextCompat;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.List;
+import java.util.Observable;
+import java.util.Observer;
+
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.base.MainActivity;
+import se.leap.bitmaskclient.base.models.Location;
+import se.leap.bitmaskclient.base.utils.PreferenceHelper;
+import se.leap.bitmaskclient.base.views.IconSwitchEntry;
+import se.leap.bitmaskclient.eip.EipCommand;
+import se.leap.bitmaskclient.eip.EipStatus;
+import se.leap.bitmaskclient.eip.GatewaysManager;
+
+import static android.content.Context.MODE_PRIVATE;
+import static android.view.View.INVISIBLE;
+import static android.view.View.VISIBLE;
+import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT;
+import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
+import static se.leap.bitmaskclient.base.models.Constants.USE_PLUGGABLE_TRANSPORTS;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.setPreferredCity;
+
+public class GatewaySelectionFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener, Observer {
+
+ private static final String TAG = GatewaySelectionFragment.class.getSimpleName();
+
+
+ private RecyclerView recyclerView;
+ private LinearLayoutManager layoutManager;
+ private LocationListAdapter locationListAdapter;
+ private IconSwitchEntry autoSelectionSwitch;
+ private AppCompatButton vpnButton;
+ private GatewaysManager gatewaysManager;
+ private SharedPreferences preferences;
+ private EipStatus eipStatus;
+
+ public GatewaySelectionFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ gatewaysManager = new GatewaysManager(getContext());
+ preferences = getContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
+ eipStatus = EipStatus.getInstance();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.f_gateway_selection, container, false);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ initRecyclerView();
+ initAutoSelectionSwitch();
+ initVpnButton();
+ eipStatus.addObserver(this);
+ preferences.registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ preferences.unregisterOnSharedPreferenceChangeListener(this);
+ eipStatus.deleteObserver(this);
+ }
+
+
+
+ private void initRecyclerView() {
+ recyclerView = (RecyclerView) getActivity().findViewById(R.id.gatewaySelection_list);
+ recyclerView.setHasFixedSize(true);
+ layoutManager = new LinearLayoutManager(this.getContext());
+ recyclerView.setLayoutManager(layoutManager);
+ locationListAdapter = new LocationListAdapter(gatewaysManager.getGatewayLocations());
+ recyclerView.setAdapter(locationListAdapter);
+ recyclerView.setVisibility(getPreferredCity(getContext()) == null ? INVISIBLE : VISIBLE);
+ }
+
+ private void initAutoSelectionSwitch() {
+ autoSelectionSwitch = getActivity().findViewById(R.id.automatic_gateway_switch);
+ autoSelectionSwitch.setSingleLine(false);
+ autoSelectionSwitch.setSubtitle(getString(R.string.gateway_selection_warning, getString(R.string.app_name)));
+ autoSelectionSwitch.setChecked(getPreferredCity(getContext()) == null);
+ autoSelectionSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ recyclerView.setVisibility(!isChecked ? VISIBLE : View.GONE);
+ Log.d(TAG, "autoselection enabled: " + isChecked);
+ if (isChecked) {
+ PreferenceHelper.setPreferredCity(getContext(), null);
+ locationListAdapter.resetSelection();
+ }
+ });
+ }
+
+ private void initVpnButton() {
+ vpnButton = getActivity().findViewById(R.id.vpn_button);
+ setVpnButtonState();
+ vpnButton.setOnClickListener(v -> {
+ EipCommand.startVPN(getContext(), false);
+ Intent intent = new Intent(getContext(), MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.setAction(ACTION_SHOW_VPN_FRAGMENT);
+ startActivity(intent);
+ });
+ }
+
+ private void setVpnButtonState() {
+ if (eipStatus.isDisconnected()) {
+ vpnButton.setText(R.string.vpn_button_turn_on);
+ } else {
+ vpnButton.setText(R.string.reconnect);
+ }
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (USE_PLUGGABLE_TRANSPORTS.equals(key)) {
+ locationListAdapter.updateData(gatewaysManager.getGatewayLocations());
+ }
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ if (o instanceof EipStatus) {
+ eipStatus = (EipStatus) o;
+ Activity activity = getActivity();
+ if (activity != null) {
+ activity.runOnUiThread(this::setVpnButtonState);
+ }
+ }
+
+ }
+
+ static class LocationListAdapter extends RecyclerView.Adapter<LocationListAdapter.ViewHolder> {
+ private static final String TAG = LocationListAdapter.class.getSimpleName();
+ private List<Location> values;
+ private Location selectedLocation = null;
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ public AppCompatTextView locationLabel;
+ public AppCompatTextView qualityLabel;
+ public AppCompatImageView checkedIcon;
+ public View layout;
+
+ public ViewHolder(View v) {
+ super(v);
+ layout = v;
+ locationLabel = (AppCompatTextView) v.findViewById(R.id.location);
+ qualityLabel = (AppCompatTextView) v.findViewById(R.id.quality);
+ checkedIcon = (AppCompatImageView) v.findViewById(R.id.checked_icon);
+ }
+ }
+
+ public void add(int position, Location item) {
+ values.add(position, item);
+ notifyItemInserted(position);
+ }
+
+ public void remove(int position) {
+ values.remove(position);
+ notifyItemRemoved(position);
+ }
+
+ public void resetSelection() {
+ if (selectedLocation != null) {
+ selectedLocation.selected = false;
+ notifyDataSetChanged();
+ }
+ }
+
+ public void updateData(List<Location> data) {
+ values = data;
+ notifyDataSetChanged();
+ }
+
+ public LocationListAdapter(List<Location> data) {
+ values = data;
+ }
+
+ @NonNull
+ @Override
+ public LocationListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(
+ parent.getContext());
+ View v = inflater.inflate(R.layout.v_select_text_list_item, parent, false);
+ return new ViewHolder(v);
+ }
+
+ // Replace the contents of a view (invoked by the layout manager)
+ @Override
+ public void onBindViewHolder(ViewHolder holder, final int position) {
+ final Location location = values.get(position);
+ holder.locationLabel.setText(location.name);
+ holder.layout.setOnClickListener(v -> {
+ Log.d(TAG, "view at position clicked: " + position);
+ if (selectedLocation == null) {
+ selectedLocation = location;
+ selectedLocation.selected = true;
+ } else if (selectedLocation.equals(location.name)){
+ selectedLocation.selected = !selectedLocation.selected;
+ } else {
+ selectedLocation.selected = false;
+ selectedLocation = location;
+ selectedLocation.selected = true;
+ }
+ setPreferredCity(holder.layout.getContext(), selectedLocation.selected ? selectedLocation.name : null);
+ holder.checkedIcon.setVisibility(selectedLocation.selected ? VISIBLE : INVISIBLE);
+ notifyDataSetChanged();
+ });
+ Drawable checkIcon = DrawableCompat.wrap(holder.layout.getContext().getResources().getDrawable(R.drawable.ic_check_bold)).mutate();
+ DrawableCompat.setTint(checkIcon, ContextCompat.getColor(holder.layout.getContext(), R.color.colorSuccess));
+ holder.checkedIcon.setImageDrawable(checkIcon);
+ holder.checkedIcon.setVisibility(location.selected ? VISIBLE : INVISIBLE);
+ holder.qualityLabel.setText(getQualityString(location.averageLoad));
+ if (location.selected) {
+ selectedLocation = location;
+ }
+ }
+
+ public String getQualityString(double quality) {
+ if (quality == 0) {
+ return "";
+ } else if (quality < 0.25) {
+ return "BAD";
+ } else if (quality < 0.6) {
+ return "AVERAGE";
+ } else if (quality < 0.8){
+ return "GOOD";
+ } else {
+ return "EXCELLENT";
+ }
+ }
+
+
+ @Override
+ public int getItemCount() {
+ return values.size();
+ }
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
index 2ce0e597..cbfe8a71 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java
@@ -70,6 +70,7 @@ import static android.view.View.VISIBLE;
import static se.leap.bitmaskclient.base.BitmaskApp.getRefWatcher;
import static se.leap.bitmaskclient.base.models.Constants.DONATION_URL;
import static se.leap.bitmaskclient.base.models.Constants.ENABLE_DONATION;
+import static se.leap.bitmaskclient.base.models.Constants.PREFERRED_CITY;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER;
import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
@@ -79,6 +80,7 @@ import static se.leap.bitmaskclient.R.string.about_fragment_title;
import static se.leap.bitmaskclient.R.string.exclude_apps_fragment_title;
import static se.leap.bitmaskclient.R.string.log_fragment_title;
import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity;
import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getSaveBattery;
import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getShowAlwaysOnDialog;
import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports;
@@ -114,6 +116,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen
private IconSwitchEntry saveBattery;
private IconTextEntry tethering;
private IconSwitchEntry firewall;
+ private IconTextEntry manualGatewaySelection;
private View experimentalFeatureFooter;
private boolean userLearnedDrawer;
@@ -255,6 +258,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen
initSaveBatteryEntry();
initAlwaysOnVpnEntry();
initExcludeAppsEntry();
+ initManualGatewayEntry();
initShowExperimentalHint();
initTetheringEntry();
initFirewallEntry();
@@ -412,6 +416,23 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen
});
}
+ private void initManualGatewayEntry() {
+ manualGatewaySelection = drawerView.findViewById(R.id.manualGatewaySelection);
+ String preferredGateway = getPreferredCity(getContext());
+ String subtitle = preferredGateway != null ? preferredGateway : getString(R.string.gateway_selection_best_location);
+ manualGatewaySelection.setSubtitle(subtitle);
+ boolean show = ProviderObservable.getInstance().getCurrentProvider().hasGatewaysInDifferentLocations();
+ manualGatewaySelection.setVisibility(show ? VISIBLE : GONE);
+
+ manualGatewaySelection.setOnClickListener(v -> {
+ FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager());
+ closeDrawer();
+ Fragment fragment = new GatewaySelectionFragment();
+ setActionBarTitle(R.string.gateway_selection_title);
+ fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG);
+ });
+ }
+
private void initTetheringEntry() {
tethering = drawerView.findViewById(R.id.tethering);
boolean show = showExperimentalFeatures(getContext());
@@ -627,6 +648,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen
Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider();
account.setText(currentProvider.getName());
initUseBridgesEntry();
+ initManualGatewayEntry();
}
private void updateExcludeAppsSubtitle(IconTextEntry excludeApps, int number) {
@@ -651,6 +673,8 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen
initUseBridgesEntry();
} else if (key.equals(USE_IPv6_FIREWALL)) {
initFirewallEntry();
+ } else if (key.equals(PREFERRED_CITY)) {
+ initManualGatewayEntry();
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Location.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Location.java
new file mode 100644
index 00000000..ae7818ba
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Location.java
@@ -0,0 +1,42 @@
+package se.leap.bitmaskclient.base.models;
+
+import androidx.annotation.NonNull;
+
+public class Location {
+ @NonNull public String name;
+ public double averageLoad;
+ public int numberOfGateways;
+ public boolean selected;
+
+ public Location(@NonNull String name, double averageLoad, int numberOfGateways, boolean selected) {
+ this.name = name;
+ this.averageLoad = averageLoad;
+ this.numberOfGateways = numberOfGateways;
+ this.selected = selected;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Location)) return false;
+
+ Location location = (Location) o;
+
+ if (Double.compare(location.averageLoad, averageLoad) != 0) return false;
+ if (numberOfGateways != location.numberOfGateways) return false;
+ if (selected != location.selected) return false;
+ return name.equals(location.name);
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ result = name.hashCode();
+ temp = Double.doubleToLongBits(averageLoad);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ result = 31 * result + numberOfGateways;
+ result = 31 * result + (selected ? 1 : 0);
+ return result;
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
index 97f1019b..5d8b4e5d 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
@@ -32,6 +32,7 @@ import java.util.Locale;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES;
import static se.leap.bitmaskclient.base.models.Constants.GATEWAYS;
+import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOWED_REGISTERED;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_ALLOW_ANONYMOUS;
import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT;
@@ -333,6 +334,14 @@ public final class Provider implements Parcelable {
&& !getEipServiceJson().has(ERRORS);
}
+ public boolean hasGatewaysInDifferentLocations() {
+ try {
+ return getEipServiceJson().getJSONObject(LOCATIONS).length() > 1;
+ } catch (NullPointerException | JSONException e) {
+ return false;
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java b/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java
index 1160986e..628b4c15 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/views/IconSwitchEntry.java
@@ -1,3 +1,19 @@
+/**
+ * Copyright (c) 2021 LEAP Encryption Access Project and contributors
+ *
+ * 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.base.views;
import android.annotation.TargetApi;
@@ -51,7 +67,7 @@ public class IconSwitchEntry extends LinearLayout {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rootview = inflater.inflate(R.layout.v_switch_list_item, this, true);
- textView = rootview.findViewById(android.R.id.text1);
+ textView = rootview.findViewById(R.id.title);
subtitleView = rootview.findViewById(R.id.subtitle);
iconView = rootview.findViewById(R.id.material_icon);
switchView = rootview.findViewById(R.id.option_switch);
@@ -88,6 +104,15 @@ public class IconSwitchEntry extends LinearLayout {
textView.setText(id);
}
+ public void setSubtitle(CharSequence text) {
+ subtitleView.setText(text);
+ }
+
+ public void setSingleLine(boolean singleLine) {
+ textView.setSingleLine(singleLine);
+ subtitleView.setSingleLine(singleLine);
+ }
+
public void showSubtitle(boolean show) {
subtitleView.setVisibility(show ? VISIBLE : GONE);
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
index 763b5449..78b33355 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
@@ -35,12 +35,14 @@ import de.blinkt.openvpn.core.ConfigParser;
import de.blinkt.openvpn.core.connection.Connection;
import se.leap.bitmaskclient.base.utils.PreferenceHelper;
+import static se.leap.bitmaskclient.base.models.Constants.FULLNESS;
import static se.leap.bitmaskclient.base.models.Constants.HOST;
import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS;
import static se.leap.bitmaskclient.base.models.Constants.LOCATION;
import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS;
import static se.leap.bitmaskclient.base.models.Constants.NAME;
import static se.leap.bitmaskclient.base.models.Constants.OPENVPN_CONFIGURATION;
+import static se.leap.bitmaskclient.base.models.Constants.OVERLOAD;
import static se.leap.bitmaskclient.base.models.Constants.TIMEZONE;
import static se.leap.bitmaskclient.base.models.Constants.VERSION;
@@ -146,6 +148,26 @@ public class Gateway {
}
}
+ public boolean hasLoadInfo() {
+ return load != null;
+ }
+
+ public double getFullness() {
+ try {
+ return load.getDouble(FULLNESS);
+ } catch (JSONException | NullPointerException e) {
+ return 0;
+ }
+ }
+
+ public boolean isOverloaded() {
+ try {
+ return load.getBoolean(OVERLOAD);
+ } catch (JSONException | NullPointerException e) {
+ return false;
+ }
+ }
+
/**
* Create and attach the VpnProfile to our gateway object
*/
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 b06e894e..5f6910bf 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
@@ -30,6 +30,8 @@ import org.json.JSONObject;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -37,8 +39,10 @@ import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.ConfigParser;
import de.blinkt.openvpn.core.VpnStatus;
import de.blinkt.openvpn.core.connection.Connection;
+import se.leap.bitmaskclient.base.models.Location;
import se.leap.bitmaskclient.base.models.Provider;
import se.leap.bitmaskclient.base.models.ProviderObservable;
+import se.leap.bitmaskclient.base.utils.PreferenceHelper;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
@@ -72,15 +76,46 @@ public class GatewaysManager {
* @return the n closest Gateway
*/
public Gateway select(int nClosest) {
- Connection.TransportType transportType = getUsePluggableTransports(context) ? OBFS4 : OPENVPN;
String selectedCity = getPreferredCity(context);
+ return select(nClosest, selectedCity);
+ }
+
+ public Gateway select(int nClosest, String city) {
+ Connection.TransportType transportType = getUsePluggableTransports(context) ? OBFS4 : OPENVPN;
if (presortedList.size() > 0) {
- return getGatewayFromPresortedList(nClosest, transportType, selectedCity);
+ return getGatewayFromPresortedList(nClosest, transportType, city);
}
- return getGatewayFromTimezoneCalculation(nClosest, transportType, selectedCity);
+ return getGatewayFromTimezoneCalculation(nClosest, transportType, city);
}
+ public List<Location> getGatewayLocations() {
+ String selectedCity = PreferenceHelper.getPreferredCity(context);
+ HashMap<String, Integer> locationNames = new HashMap<>();
+ ArrayList<Location> locations = new ArrayList<>();
+ int n = 0;
+ Gateway gateway;
+ while ((gateway = select(n, null)) != null) {
+ if (!locationNames.containsKey(gateway.getName())) {
+ locationNames.put(gateway.getName(), locations.size());
+ Location location = new Location(
+ gateway.getName(),
+ gateway.getFullness(),
+ 1,
+ gateway.getName().equals(selectedCity));
+ locations.add(location);
+ } else {
+ int index = locationNames.get(gateway.getName());
+ Location location = locations.get(index);
+ location.averageLoad = (location.numberOfGateways * location.averageLoad + gateway.getFullness()) / (location.numberOfGateways + 1);
+ location.numberOfGateways += 1;
+ locations.set(index, location);
+ }
+ n++;
+ }
+
+ return locations;
+ }
private Gateway getGatewayFromTimezoneCalculation(int nClosest, Connection.TransportType transportType, @Nullable String city) {
List<Gateway> list = new ArrayList<>(gateways.values());