diff options
| author | Ted Romer <tromer@gmail.com> | 2025-09-26 02:22:57 -0700 |
|---|---|---|
| committer | Arne Schwabe <arne@rfc2549.org> | 2026-01-23 13:43:56 +0100 |
| commit | 1f1dae89983ed10b7913638e2153d4d3bcb3ac75 (patch) | |
| tree | 986bdb4b6257a193f0a5cec0232a4fa82d07708b | |
| parent | 14c315f5a1d526fcd0b36323e724d4e199c6d137 (diff) | |
Add setDefaultProfile and getDefaultProfile methods to API.
Also:
* add corresponding setDefaultVPN intent
* add sample usage to remoteExample
* bring remoteExample/.../IOpenVPNAPIService.aidl into sync with main/...
* make APIVpnProfile.java in remoteExample/ and main/ identical.
10 files changed, 134 insertions, 24 deletions
diff --git a/main/src/main/AndroidManifest.xml b/main/src/main/AndroidManifest.xml index 383533fd..4aa454c1 100644 --- a/main/src/main/AndroidManifest.xml +++ b/main/src/main/AndroidManifest.xml @@ -139,6 +139,10 @@ android:name=".api.ResumeVPN" android:exported="true" android:targetActivity=".api.RemoteAction" /> + <activity-alias + android:name=".api.SetDefaultVPN" + android:exported="true" + android:targetActivity=".api.RemoteAction" /> </application> diff --git a/main/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNAPIService.aidl b/main/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNAPIService.aidl index 39b53106..5e259d29 100644 --- a/main/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNAPIService.aidl +++ b/main/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNAPIService.aidl @@ -73,4 +73,10 @@ interface IOpenVPNAPIService { * up to now the only extra that can be put is a boolean "de.blinkt.openvpn.api.ALLOW_VPN_BYPASS"
*/
APIVpnProfile addNewVPNProfileWithExtras (String name, boolean userEditable, String config, in Bundle extras);
+
+ /** Get the current default profile, or null if there is no default */
+ @nullable APIVpnProfile getDefaultProfile();
+
+ /** Set the default profile by UUID */
+ void setDefaultProfile (String profileUUID);
}
\ No newline at end of file diff --git a/main/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java b/main/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java index 9d47c01d..cf310dbc 100644 --- a/main/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java +++ b/main/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java @@ -47,8 +47,8 @@ public class APIVpnProfile implements Parcelable { //dest.writeString(mProfileCreator);
}
- public static final Parcelable.Creator<APIVpnProfile> CREATOR
- = new Parcelable.Creator<APIVpnProfile>() {
+ public static final Creator<APIVpnProfile> CREATOR
+ = new Creator<APIVpnProfile>() {
public APIVpnProfile createFromParcel(Parcel in) {
return new APIVpnProfile(in);
}
diff --git a/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java b/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java index bdf3e29c..479f329a 100644 --- a/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java +++ b/main/src/main/java/de/blinkt/openvpn/api/ExternalOpenVPNService.java @@ -13,6 +13,7 @@ import android.content.Context; import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.SharedPreferences;
import android.net.VpnService;
import android.os.Binder;
import android.os.Build;
@@ -37,6 +38,7 @@ import de.blinkt.openvpn.core.ConfigParser.ConfigParseError; import de.blinkt.openvpn.core.ConnectionStatus;
import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
import de.blinkt.openvpn.core.OpenVPNService;
+import de.blinkt.openvpn.core.Preferences;
import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.core.VPNLaunchHelper;
import de.blinkt.openvpn.core.VpnStatus;
@@ -109,6 +111,10 @@ public class ExternalOpenVPNService extends Service implements StateListener { }
+ private APIVpnProfile apiVpnProfileFromVpnProfile(VpnProfile vp) {
+ return new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable, vp.mProfileCreator);
+ }
+
private final IOpenVPNAPIService.Stub mBinder = new IOpenVPNAPIService.Stub() {
@Override
@@ -121,7 +127,7 @@ public class ExternalOpenVPNService extends Service implements StateListener { for (VpnProfile vp : pm.getProfiles()) {
if (!vp.profileDeleted)
- profiles.add(new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable, vp.mProfileCreator));
+ profiles.add(apiVpnProfileFromVpnProfile(vp));
}
return profiles;
@@ -232,7 +238,7 @@ public class ExternalOpenVPNService extends Service implements StateListener { vp.addChangeLogEntry("AIDL API created profile");
pm.saveProfile(ExternalOpenVPNService.this, vp);
pm.saveProfileList(ExternalOpenVPNService.this);
- return new APIVpnProfile(vp.getUUIDString(), vp.mName, vp.mUserEditable, vp.mProfileCreator);
+ return apiVpnProfileFromVpnProfile(vp);
} catch (IOException e) {
VpnStatus.logException(e);
return null;
@@ -330,6 +336,37 @@ public class ExternalOpenVPNService extends Service implements StateListener { mService.userPause(false);
}
+
+ @Override
+ public APIVpnProfile getDefaultProfile() throws RemoteException {
+ mExtAppDb.checkOpenVPNPermission(getPackageManager());
+ ProfileManager pm = ProfileManager.getInstance(getBaseContext());
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(getBaseContext());
+ String profileUUID = prefs.getString("alwaysOnVpn", null);
+ if (profileUUID == null) {
+ return null;
+ }
+ VpnProfile vp = ProfileManager.get(getBaseContext(), profileUUID);
+ if (vp.checkProfile(getApplicationContext()) != R.string.no_error_found) {
+ VpnStatus.logInfo("Default profile is currently set to unknown UUID " + profileUUID);
+ return null;
+ }
+ APIVpnProfile result = apiVpnProfileFromVpnProfile(vp);
+ return result;
+ }
+
+ @Override
+ public void setDefaultProfile(String profileUUID) throws RemoteException {
+ mExtAppDb.checkOpenVPNPermission(getPackageManager());
+ ProfileManager pm = ProfileManager.getInstance(getBaseContext());
+ VpnProfile vp = ProfileManager.get(getBaseContext(), profileUUID);
+ if (vp.checkProfile(getApplicationContext()) != R.string.no_error_found)
+ throw new RemoteException(getString(vp.checkProfile(getApplicationContext())));
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(getBaseContext());
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString("alwaysOnVpn", vp.getUUIDString());
+ editor.apply();
+ }
};
diff --git a/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java b/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java index 55322e7f..f2ff1735 100644 --- a/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java +++ b/main/src/main/java/de/blinkt/openvpn/api/RemoteAction.java @@ -10,6 +10,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -19,7 +20,9 @@ import de.blinkt.openvpn.LaunchVPN; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; +import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.core.VpnStatus; public class RemoteAction extends Activity { @@ -61,6 +64,33 @@ public class RemoteAction extends Activity { } + private void connectVPN(Intent intent) { + String vpnName = intent.getStringExtra(EXTRA_NAME); + VpnProfile profile = ProfileManager.getInstance(this).getProfileByName(vpnName); + if (profile == null) { + Toast.makeText(this, String.format("Vpn profile %s from API call not found", vpnName), Toast.LENGTH_LONG).show(); + } else { + Intent startVPN = new Intent(this, LaunchVPN.class); + startVPN.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString()); + startVPN.putExtra(OpenVPNService.EXTRA_START_REASON, ".api.ConnectVPN call"); + startVPN.setAction(Intent.ACTION_MAIN); + startActivity(startVPN); + } + } + + private void setDefaultVPN(Intent intent) { + String defaultVpnName = intent.getStringExtra(EXTRA_NAME); + VpnProfile defaultProfile = ProfileManager.getInstance(this).getProfileByName(defaultVpnName); + if (defaultProfile == null) { + Toast.makeText(this, String.format("Vpn profile %s from API call not found", defaultVpnName), Toast.LENGTH_LONG).show(); + } else { + SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString("alwaysOnVpn", defaultProfile.getUUIDString()); + editor.apply(); + } + } + private void performAction() throws RemoteException { if (!mService.isAllowedExternalApp(getCallingPackage())) { @@ -86,17 +116,10 @@ public class RemoteAction extends Activity { mService.userPause(false); break; case ".api.ConnectVPN": - String vpnName = intent.getStringExtra(EXTRA_NAME); - VpnProfile profile = ProfileManager.getInstance(this).getProfileByName(vpnName); - if (profile == null) { - Toast.makeText(this, String.format("Vpn profile %s from API call not found", vpnName), Toast.LENGTH_LONG).show(); - } else { - Intent startVPN = new Intent(this, LaunchVPN.class); - startVPN.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUID().toString()); - startVPN.putExtra(OpenVPNService.EXTRA_START_REASON, ".api.ConnectVPN call"); - startVPN.setAction(Intent.ACTION_MAIN); - startActivity(startVPN); - } + connectVPN(intent); + break; + case ".api.SetDefaultVPN": + setDefaultVPN(intent); break; } finish(); diff --git a/remoteExample/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNAPIService.aidl b/remoteExample/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNAPIService.aidl index 1989b771..5e259d29 100644 --- a/remoteExample/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNAPIService.aidl +++ b/remoteExample/src/main/aidl/de/blinkt/openvpn/api/IOpenVPNAPIService.aidl @@ -18,14 +18,14 @@ interface IOpenVPNAPIService { boolean addVPNProfile (String name, String config);
/** start a profile using a config as inline string. Make sure that all needed data is inlined,
- * e.g., using <ca>...</ca> or <auth-user-data>...</auth-user-data>
+ * e.g., using <ca>...</ca> or <auth-user-pass>...</auth-user-pass>
* See the OpenVPN manual page for more on inlining files */
- void startVPN (String inlineconfig);
+ void startVPN (in String inlineconfig);
/** This permission framework is used to avoid confused deputy style attack to the VPN
* calling this will give null if the app is allowed to use the external API and an Intent
* that can be launched to request permissions otherwise */
- Intent prepare (String packagename);
+ Intent prepare (in String packagename);
/** Used to trigger to the Android VPN permission dialog (VPNService.prepare()) in advance,
* if this return null OpenVPN for ANdroid already has the permissions otherwise you can start the returned Intent
@@ -44,15 +44,15 @@ interface IOpenVPNAPIService { /**
* Registers to receive OpenVPN Status Updates
*/
- void registerStatusCallback(IOpenVPNStatusCallback cb);
+ void registerStatusCallback(in IOpenVPNStatusCallback cb);
/**
* Remove a previously registered callback interface.
*/
- void unregisterStatusCallback(IOpenVPNStatusCallback cb);
+ void unregisterStatusCallback(in IOpenVPNStatusCallback cb);
/** Remove a profile by UUID */
- void removeProfile (String profileUUID);
+ void removeProfile (in String profileUUID);
/** Request a socket to be protected as a VPN socket would be. Useful for creating
* a helper socket for an app controlling OpenVPN
@@ -64,9 +64,19 @@ interface IOpenVPNAPIService { /** Use a profile with all certificates etc. embedded */
APIVpnProfile addNewVPNProfile (String name, boolean userEditable, String config);
+ /** Same as startVPN(String), but also takes a Bundle with extra parameters,
+ * which will be applied to the created VPNProfile (e.g. allow vpn bypass). */
+ void startVPNwithExtras(in String inlineconfig, in Bundle extras);
+
/** Same as addNewVPNProfile(String, boolean, String) but giving possibility to pass a Bundle like
* in startVPNwithExtras(String, Bundle) to apply e.g. "allow vpn bypass" to profile.
* up to now the only extra that can be put is a boolean "de.blinkt.openvpn.api.ALLOW_VPN_BYPASS"
*/
APIVpnProfile addNewVPNProfileWithExtras (String name, boolean userEditable, String config, in Bundle extras);
+
+ /** Get the current default profile, or null if there is no default */
+ @nullable APIVpnProfile getDefaultProfile();
+
+ /** Set the default profile by UUID */
+ void setDefaultProfile (String profileUUID);
}
\ No newline at end of file diff --git a/remoteExample/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java b/remoteExample/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java index 65c6ad57..cf310dbc 100644 --- a/remoteExample/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java +++ b/remoteExample/src/main/java/de/blinkt/openvpn/api/APIVpnProfile.java @@ -1,6 +1,8 @@ /*
- * Copyright (c) 2012-2015 Arne Schwabe
- * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * This file is used for implementing the external API and this file like the AIDL and is exempted
+ * from the GPLv2.
+ *
*/
package de.blinkt.openvpn.api;
diff --git a/remoteExample/src/main/java/de/blinkt/openvpn/remote/MainFragment.java b/remoteExample/src/main/java/de/blinkt/openvpn/remote/MainFragment.java index d98fdedd..c4e92f27 100644 --- a/remoteExample/src/main/java/de/blinkt/openvpn/remote/MainFragment.java +++ b/remoteExample/src/main/java/de/blinkt/openvpn/remote/MainFragment.java @@ -52,6 +52,7 @@ public class MainFragment extends Fragment implements View.OnClickListener, Hand public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_main, container, false); v.findViewById(R.id.disconnect).setOnClickListener(this); + v.findViewById(R.id.setDefaultProfile).setOnClickListener(this); v.findViewById(R.id.getMyIP).setOnClickListener(this); v.findViewById(R.id.startembedded).setOnClickListener(this); v.findViewById(R.id.addNewProfile).setOnClickListener(this); @@ -70,6 +71,7 @@ public class MainFragment extends Fragment implements View.OnClickListener, Hand private static final int MSG_UPDATE_MYIP = 1; private static final int START_PROFILE_EMBEDDED = 2; private static final int START_PROFILE_BYUUID = 3; + private static final int SET_DEFAULT_PROFILE_BYUUID = 4; private static final int ICS_OPENVPN_PERMISSION = 7; private static final int PROFILE_ADD_NEW = 8; private static final int PROFILE_ADD_NEW_EDIT = 9; @@ -195,9 +197,12 @@ public class MainFragment extends Fragment implements View.OnClickListener, Hand try { List<APIVpnProfile> list = mService.getProfiles(); + APIVpnProfile defaultProfile = mService.getDefaultProfile(); + String defaultUUID = defaultProfile != null ? defaultProfile.mUUID : null; String all="List:"; for(APIVpnProfile vp:list.subList(0, Math.min(5, list.size()))) { - all = all + vp.mName + ":" + vp.mUUID + "\n"; + String suffix = (vp.mUUID.equals(defaultUUID)) ? " (default)" : ""; + all = all + vp.mName + ":" + vp.mUUID + suffix + "\n"; } if (list.size() > 5) @@ -253,6 +258,14 @@ public class MainFragment extends Fragment implements View.OnClickListener, Hand e.printStackTrace(); } break; + case R.id.setDefaultProfile: + try { + prepareStartProfile(SET_DEFAULT_PROFILE_BYUUID); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + break; case R.id.getMyIP: // Socket handling is not allowed on main thread @@ -317,6 +330,12 @@ public class MainFragment extends Fragment implements View.OnClickListener, Hand } catch (RemoteException e) { e.printStackTrace(); } + if(requestCode==SET_DEFAULT_PROFILE_BYUUID) + try { + mService.setDefaultProfile(mStartUUID); + } catch (RemoteException e) { + e.printStackTrace(); + } if (requestCode == ICS_OPENVPN_PERMISSION) { listVPNs(); try { diff --git a/remoteExample/src/main/res/layout/fragment_main.xml b/remoteExample/src/main/res/layout/fragment_main.xml index 6b8102e2..0780bba4 100644 --- a/remoteExample/src/main/res/layout/fragment_main.xml +++ b/remoteExample/src/main/res/layout/fragment_main.xml @@ -64,6 +64,14 @@ android:text="@string/disconnect" /> <Button + android:id="@+id/setDefaultProfile" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignBottom="@+id/disconnect" + android:layout_toRightOf="@+id/disconnect" + android:text="@string/set_default" /> + + <Button android:id="@+id/startembedded" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" diff --git a/remoteExample/src/main/res/values/strings.xml b/remoteExample/src/main/res/values/strings.xml index 971f7de5..44c9ecc8 100644 --- a/remoteExample/src/main/res/values/strings.xml +++ b/remoteExample/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ <string name="no_now">Not now</string> <string name="show_my_ip">Show my IP</string> <string name="disconnect">Disconnect</string> + <string name="set_default">Set Default</string> <string name="start_embedded">Start embedded profile</string> <string name="addNew">Add new Profile</string> <string name="addNewEdit">Add editable profile</string> |
