diff options
| -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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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.pngBinary files differ new 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> | 
