diff options
author | Arne Schwabe <arne@rfc2549.org> | 2016-12-17 11:07:40 +0100 |
---|---|---|
committer | Arne Schwabe <arne@rfc2549.org> | 2016-12-17 11:07:40 +0100 |
commit | 6b17792191691495f1423a29cd8bbb97443d5328 (patch) | |
tree | 0c71e07b45493277bfaac939cf467b7e9c1b8653 | |
parent | a13d3764b9731ddc3871c4638c185a38d6f418b9 (diff) |
Implement LRU sorting for profile list
-rw-r--r-- | main/src/main/java/de/blinkt/openvpn/LaunchVPN.java | 1 | ||||
-rw-r--r-- | main/src/main/java/de/blinkt/openvpn/VpnProfile.java | 15 | ||||
-rw-r--r-- | main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java | 14 | ||||
-rw-r--r-- | main/src/main/java/de/blinkt/openvpn/core/ProfileManager.java | 16 | ||||
-rw-r--r-- | main/src/main/java/de/blinkt/openvpn/fragments/VPNProfileList.java | 176 | ||||
-rw-r--r-- | main/src/main/res/drawable-hdpi/ic_sort_white_24dp.png | bin | 0 -> 115 bytes | |||
-rw-r--r-- | main/src/main/res/drawable-mdpi/ic_sort_white_24dp.png | bin | 0 -> 90 bytes | |||
-rw-r--r-- | main/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png | bin | 0 -> 101 bytes | |||
-rw-r--r-- | main/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png | bin | 0 -> 103 bytes | |||
-rw-r--r-- | main/src/main/res/drawable-xxxhdpi/ic_sort_white_24dp.png | bin | 0 -> 107 bytes | |||
-rw-r--r-- | main/src/main/res/values-v21/refs.xml | 2 | ||||
-rw-r--r-- | main/src/main/res/values/refs.xml | 2 | ||||
-rwxr-xr-x | main/src/main/res/values/strings.xml | 5 |
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 Binary files differnew file mode 100644 index 00000000..55a429b6 --- /dev/null +++ b/main/src/main/res/drawable-hdpi/ic_sort_white_24dp.png 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 Binary files differnew file mode 100644 index 00000000..3d84a444 --- /dev/null +++ b/main/src/main/res/drawable-mdpi/ic_sort_white_24dp.png 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 Binary files differnew file mode 100644 index 00000000..6d4af1bc --- /dev/null +++ b/main/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png 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 Binary files differnew file mode 100644 index 00000000..b8ef1050 --- /dev/null +++ b/main/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png 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 Binary files differnew file mode 100644 index 00000000..4796c33b --- /dev/null +++ b/main/src/main/res/drawable-xxxhdpi/ic_sort_white_24dp.png 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> |