summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2016-12-17 11:07:40 +0100
committerArne Schwabe <arne@rfc2549.org>2016-12-17 11:07:40 +0100
commit6b17792191691495f1423a29cd8bbb97443d5328 (patch)
tree0c71e07b45493277bfaac939cf467b7e9c1b8653
parenta13d3764b9731ddc3871c4638c185a38d6f418b9 (diff)
Implement LRU sorting for profile list
-rw-r--r--main/src/main/java/de/blinkt/openvpn/LaunchVPN.java1
-rw-r--r--main/src/main/java/de/blinkt/openvpn/VpnProfile.java15
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java14
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java16
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java176
-rw-r--r--main/src/main/res/drawable-hdpi/ic_sort_white_24dp.pngbin0 -> 115 bytes
-rw-r--r--main/src/main/res/drawable-mdpi/ic_sort_white_24dp.pngbin0 -> 90 bytes
-rw-r--r--main/src/main/res/drawable-xhdpi/ic_sort_white_24dp.pngbin0 -> 101 bytes
-rw-r--r--main/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.pngbin0 -> 103 bytes
-rw-r--r--main/src/main/res/drawable-xxxhdpi/ic_sort_white_24dp.pngbin0 -> 107 bytes
-rw-r--r--main/src/main/res/values-v21/refs.xml2
-rw-r--r--main/src/main/res/values/refs.xml2
-rwxr-xr-xmain/src/main/res/values/strings.xml5
13 files changed, 221 insertions, 10 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java
index 5eb44953..7545869f 100644
--- a/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java
+++ b/main/src/main/java/de/blinkt/openvpn/LaunchVPN.java
@@ -238,6 +238,7 @@ public class LaunchVPN extends Activity {
if (!mhideLog && showLogWindow)
showLogWindow();
+ ProfileManager.updateLRU(this, mSelectedProfile);
VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext());
finish();
}
diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 4c03f686..38ecb73f 100644
--- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -162,6 +162,9 @@ public class VpnProfile implements Serializable, Cloneable {
public int mVersion = 0;
+ // timestamp when the profile was last used
+ public long mLastUsed;
+
/* Options no longer used in new profiles */
public String mServerName = "openvpn.example.com";
public String mServerPort = "1194";
@@ -175,6 +178,7 @@ public class VpnProfile implements Serializable, Cloneable {
mConnections = new Connection[1];
mConnections[0] = new Connection();
+ mLastUsed = System.currentTimeMillis();
}
public static String openVpnEscape(String unescaped) {
@@ -192,6 +196,17 @@ public class VpnProfile implements Serializable, Cloneable {
return '"' + escapedString + '"';
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof VpnProfile) {
+ VpnProfile vpnProfile = (VpnProfile) obj;
+ return mUuid.equals(vpnProfile.mUuid);
+ } else {
+ return false;
+ }
+ }
+
public void clearDefaults() {
mServerName = "unknown";
mUsePull = false;
diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
index f0d739c3..5866cd00 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -17,6 +17,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.content.pm.ShortcutManager;
import android.content.res.Configuration;
import android.net.ConnectivityManager;
import android.net.VpnService;
@@ -30,6 +31,7 @@ import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.preference.PreferenceManager;
+import android.support.annotation.RequiresApi;
import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Log;
@@ -421,6 +423,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
int profileVersion = intent.getIntExtra(getPackageName() + ".profileVersion", 0);
// Try for 10s to get current version of the profile
mProfile = ProfileManager.get(this, profileUUID, profileVersion, 100);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+ updateShortCutUsage(mProfile);
+ }
+
} else {
/* The intent is null when we are set as always-on or the service has been restarted. */
mProfile = ProfileManager.getLastConnectedProfile(this);
@@ -455,6 +461,14 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
return START_STICKY;
}
+ @RequiresApi(Build.VERSION_CODES.N_MR1)
+ private void updateShortCutUsage(VpnProfile profile) {
+ if (profile == null)
+ return;
+ ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
+ shortcutManager.reportShortcutUsed(profile.getUUIDString());
+ }
+
private void startOpenVPN() {
VpnStatus.logInfo(R.string.building_configration);
VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, ConnectionStatus.LEVEL_START);
diff --git a/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java b/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
index 15ff7651..bce5e288 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java
@@ -9,6 +9,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.content.pm.ShortcutManager;
import android.preference.PreferenceManager;
import java.io.IOException;
@@ -133,9 +134,14 @@ public class ProfileManager {
return mLastConnectedVpn == tmpprofile;
}
-
public void saveProfile(Context context, VpnProfile profile) {
- profile.mVersion += 1;
+ saveProfile(context, profile, true);
+ }
+
+ private void saveProfile(Context context, VpnProfile profile, boolean updateVersion) {
+
+ if (updateVersion)
+ profile.mVersion += 1;
ObjectOutputStream vpnfile;
try {
vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"), Activity.MODE_PRIVATE));
@@ -226,4 +232,10 @@ public class ProfileManager {
return get(uuid);
}
+
+ public static void updateLRU(Context c, VpnProfile profile) {
+ profile.mLastUsed = System.currentTimeMillis();
+ // LRU does not change the profile, no need for the service to refresh
+ getInstance(c).saveProfile(c, profile, false);
+ }
}
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java b/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java
index 2aaf81c6..3c79a278 100644
--- a/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java
@@ -12,21 +12,40 @@ import android.app.ListFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.annotation.RequiresApi;
import android.text.Html;
import android.text.Html.ImageGetter;
-import android.view.*;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
import android.view.View.OnClickListener;
+import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
-import de.blinkt.openvpn.*;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TreeSet;
+
+import de.blinkt.openvpn.LaunchVPN;
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.activities.ConfigConverter;
import de.blinkt.openvpn.activities.DisconnectVPN;
import de.blinkt.openvpn.activities.FileSelect;
@@ -35,9 +54,7 @@ import de.blinkt.openvpn.core.ConnectionStatus;
import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.core.VpnStatus;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.TreeSet;
+import static de.blinkt.openvpn.core.OpenVPNService.DISCONNECT_VPN;
public class VPNProfileList extends ListFragment implements OnClickListener, VpnStatus.StateListener {
@@ -53,6 +70,8 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
private static final int FILE_PICKER_RESULT_KITKAT = 392;
private static final int MENU_IMPORT_PROFILE = Menu.FIRST + 1;
+ private static final int MENU_CHANGE_SORTING = Menu.FIRST + 2;
+ private static final String PREF_SORT_BY_LRU = "sortProfilesByLRU";
private String mLastStatusMessage;
@Override
@@ -134,9 +153,96 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
+ }
+
+
+ @RequiresApi(api = Build.VERSION_CODES.N_MR1)
+ void updateDynamicShortcuts() {
+
+ ShortcutManager shortcutManager = getContext().getSystemService(ShortcutManager.class);
+ if (shortcutManager.isRateLimitingActive())
+ return;
+
+ List<ShortcutInfo> shortcuts = shortcutManager.getDynamicShortcuts();
+ int maxvpn = shortcutManager.getMaxShortcutCountPerActivity() - 1;
+
+ ShortcutInfo disconnectShortcut = new ShortcutInfo.Builder(getContext(), "disconnectVPN")
+ .setShortLabel("Disconnect")
+ .setLongLabel("Disconnect VPN")
+ .setIntent(new Intent(getContext(), DisconnectVPN.class).setAction(DISCONNECT_VPN))
+ .setIcon(Icon.createWithResource(getContext(), R.drawable.ic_menu_close_clear_cancel))
+ .build();
+
+ LinkedList<ShortcutInfo> newShortcuts = new LinkedList<>();
+ LinkedList<String> removeShortcuts = new LinkedList<>();
+ LinkedList<String> disableShortcuts = new LinkedList<>();
+
+ boolean addDisconnect = true;
+
+
+ TreeSet<VpnProfile> sortedProfilesLRU = new TreeSet<VpnProfile>(new VpnProfileLRUComparator());
+ ProfileManager profileManager = ProfileManager.getInstance(getContext());
+ sortedProfilesLRU.addAll(profileManager.getProfiles());
+
+ LinkedList<VpnProfile> LRUProfiles = new LinkedList<>();
+
+ for (int i = 0; i < maxvpn; i++) {
+ LRUProfiles.add(sortedProfilesLRU.pollFirst());
+ }
+
+ for (ShortcutInfo shortcut : shortcuts) {
+ if (shortcut.getId().equals("disconnectVPN")) {
+ addDisconnect = false;
+ } else {
+ VpnProfile p = ProfileManager.get(getContext(), shortcut.getId());
+ if (p == null || p.profileDeleted) {
+ if (shortcut.isEnabled()) {
+ disableShortcuts.add(shortcut.getId());
+ removeShortcuts.add(shortcut.getId());
+ }
+ if (!shortcut.isPinned())
+ removeShortcuts.add(shortcut.getId());
+ } else {
+
+ if (LRUProfiles.contains(p))
+ LRUProfiles.remove(p);
+ else
+ removeShortcuts.add(p.getUUIDString());
+ //}
+ // XXX: Update Shortcut
+ }
+
+ }
+
+ }
+ if (addDisconnect)
+ newShortcuts.add(disconnectShortcut);
+ for (VpnProfile p : LRUProfiles)
+ newShortcuts.add(createShortcut(p));
+
+ if (removeShortcuts.size() > 0)
+ shortcutManager.removeDynamicShortcuts(removeShortcuts);
+ if (newShortcuts.size() > 0)
+ shortcutManager.addDynamicShortcuts(newShortcuts);
+ if (disableShortcuts.size() > 0)
+ shortcutManager.disableShortcuts(disableShortcuts, "VpnProfile does not exist anymore.");
}
+ @RequiresApi(Build.VERSION_CODES.N_MR1)
+ ShortcutInfo createShortcut(VpnProfile profile) {
+ Intent shortcutIntent = new Intent(Intent.ACTION_MAIN);
+ shortcutIntent.setClass(getActivity(), LaunchVPN.class);
+ shortcutIntent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString());
+ shortcutIntent.setAction(Intent.ACTION_MAIN);
+
+ return new ShortcutInfo.Builder(getContext(), profile.getUUIDString())
+ .setShortLabel(profile.getName())
+ .setLongLabel(getString(R.string.qs_connect, profile.getName()))
+ .setIcon(Icon.createWithResource(getContext(), R.mipmap.ic_launcher))
+ .setIntent(shortcutIntent)
+ .build();
+ }
class MiniImageGetter implements ImageGetter {
@@ -164,6 +270,9 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
public void onResume() {
super.onResume();
setListAdapter();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
+ updateDynamicShortcuts();
+ }
VpnStatus.addStateListener(this);
}
@@ -225,13 +334,43 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
}
+ static class VpnProfileLRUComparator implements Comparator<VpnProfile> {
+
+ @Override
+ public int compare(VpnProfile lhs, VpnProfile rhs) {
+ if (lhs == rhs)
+ // Catches also both null
+ return 0;
+
+ if (lhs == null)
+ return -1;
+ if (rhs == null)
+ return 1;
+
+ // Copied from Long.compare
+ return (lhs.mLastUsed < rhs.mLastUsed) ? -1 : ((lhs.mLastUsed == rhs.mLastUsed) ? 0 : 1);
+ }
+
+ }
+
+
private void setListAdapter() {
if (mArrayadapter == null) {
mArrayadapter = new VPNArrayAdapter(getActivity(), R.layout.vpn_list_item, R.id.vpn_item_title);
}
+ populateVpnList();
+ }
+
+ private void populateVpnList() {
+ boolean sortByLRU = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(PREF_SORT_BY_LRU, false);
Collection<VpnProfile> allvpn = getPM().getProfiles();
- TreeSet<VpnProfile> sortedset = new TreeSet<VpnProfile>(new VpnProfileNameComparator());
+ TreeSet<VpnProfile> sortedset;
+ if (sortByLRU)
+ sortedset= new TreeSet<>(new VpnProfileLRUComparator());
+ else
+ sortedset = new TreeSet<>(new VpnProfileNameComparator());
+
sortedset.addAll(allvpn);
mArrayadapter.clear();
mArrayadapter.addAll(sortedset);
@@ -254,6 +393,13 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
.setAlphabeticShortcut('i')
.setTitleCondensed(getActivity().getString(R.string.menu_import_short))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+
+ menu.add(0, MENU_CHANGE_SORTING, 0, R.string.change_sorting)
+ .setIcon(R.drawable.ic_sort)
+ .setAlphabeticShortcut('s')
+ .setTitleCondensed(getString(R.string.sort))
+ .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS);
+
}
@@ -265,11 +411,29 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
return true;
} else if (itemId == MENU_IMPORT_PROFILE) {
return startImportConfigFilePicker();
+ } else if (itemId == MENU_CHANGE_SORTING){
+ return changeSorting();
} else {
return super.onOptionsItemSelected(item);
}
}
+ private boolean changeSorting() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ boolean oldValue = prefs.getBoolean(PREF_SORT_BY_LRU, false);
+ SharedPreferences.Editor prefsedit = prefs.edit();
+ if (oldValue) {
+ Toast.makeText(getActivity(), R.string.sorted_az, Toast.LENGTH_SHORT).show();
+ prefsedit.putBoolean(PREF_SORT_BY_LRU, false);
+ } else {
+ prefsedit.putBoolean(PREF_SORT_BY_LRU, true);
+ Toast.makeText(getActivity(), R.string.sorted_lru, Toast.LENGTH_SHORT).show();
+ }
+ prefsedit.apply();
+ populateVpnList();
+ return true;
+ }
+
@Override
public void onClick(View v) {
switch (v.getId()) {
diff --git a/main/src/main/res/drawable-hdpi/ic_sort_white_24dp.png b/main/src/main/res/drawable-hdpi/ic_sort_white_24dp.png
new file mode 100644
index 00000000..55a429b6
--- /dev/null
+++ b/main/src/main/res/drawable-hdpi/ic_sort_white_24dp.png
Binary files differ
diff --git a/main/src/main/res/drawable-mdpi/ic_sort_white_24dp.png b/main/src/main/res/drawable-mdpi/ic_sort_white_24dp.png
new file mode 100644
index 00000000..3d84a444
--- /dev/null
+++ b/main/src/main/res/drawable-mdpi/ic_sort_white_24dp.png
Binary files differ
diff --git a/main/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png b/main/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png
new file mode 100644
index 00000000..6d4af1bc
--- /dev/null
+++ b/main/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png
Binary files differ
diff --git a/main/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png b/main/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png
new file mode 100644
index 00000000..b8ef1050
--- /dev/null
+++ b/main/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png
Binary files differ
diff --git a/main/src/main/res/drawable-xxxhdpi/ic_sort_white_24dp.png b/main/src/main/res/drawable-xxxhdpi/ic_sort_white_24dp.png
new file mode 100644
index 00000000..4796c33b
--- /dev/null
+++ b/main/src/main/res/drawable-xxxhdpi/ic_sort_white_24dp.png
Binary files differ
diff --git a/main/src/main/res/values-v21/refs.xml b/main/src/main/res/values-v21/refs.xml
index 10ff5197..ae9be13f 100644
--- a/main/src/main/res/values-v21/refs.xml
+++ b/main/src/main/res/values-v21/refs.xml
@@ -23,5 +23,5 @@
<drawable name="ic_menu_add_grey">@drawable/ic_add_circle_outline_grey600_24dp</drawable>
<drawable name="ic_menu_import_grey">@drawable/ic_archive_grey600_24dp</drawable>
<drawable name="ic_receipt">@drawable/ic_receipt_white_24dp</drawable>
-
+ <drawable name="ic_sort">@drawable/ic_sort_white_24dp</drawable>
</resources> \ No newline at end of file
diff --git a/main/src/main/res/values/refs.xml b/main/src/main/res/values/refs.xml
index 9d7d3201..c968b05c 100644
--- a/main/src/main/res/values/refs.xml
+++ b/main/src/main/res/values/refs.xml
@@ -22,6 +22,6 @@
<drawable name="ic_menu_delete_grey">@android:drawable/ic_menu_delete</drawable>
<drawable name="ic_menu_copy">@drawable/ic_menu_copy_holo_light</drawable>
<drawable name="ic_receipt">@drawable/ic_menu_log</drawable>
-
+ <drawable name="ic_sort">@android:drawable/ic_menu_sort_by_size</drawable>
</resources> \ No newline at end of file
diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml
index 9ce804b5..5b419fe5 100755
--- a/main/src/main/res/values/strings.xml
+++ b/main/src/main/res/values/strings.xml
@@ -418,4 +418,9 @@
<string name="state_waitconnectretry">Waiting %ss seconds between connection attempt</string>
<string name="nought_alwayson_warning"><![CDATA[If you did not get a VPN confirmation dialog, you have \"Always on VPN\" enabled for another app. In that case only that app is allowed to connect to a VPN. Check under Settings-> Networks more .. -> VPNS]]></string>
<string name="management_socket_closed">Connection to OpenVPN closed (%s)</string>
+ <string name="change_sorting">Change sorting</string>
+ <string name="sort">Sort</string>
+ <string name="sorted_lru">Profiles sorted by last recently used</string>
+ <string name="sorted_az">Profiles sorted by name</string>
+
</resources>