diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 897d5fdf..c2a38614 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,6 +31,10 @@
android:theme="@style/Theme.NetBird"
tools:targetApi="31">
+
The key names match those defined in res/xml/app_restrictions.xml and the + * Go SDK's ManagedConfig key constants.
+ */ +public class ManagedConfigReader { + + private static final String TAG = "ManagedConfigReader"; + + private ManagedConfigReader() { + // utility class + } + + /** + * Reads managed configuration from RestrictionsManager and returns a populated + * ManagedConfig instance. Returns null if no managed configuration is available + * or the RestrictionsManager service is unavailable. + * + * @param context Android context + * @return populated ManagedConfig, or null if no MDM config is present + */ + public static ManagedConfig read(Context context) { + RestrictionsManager restrictionsManager = + (RestrictionsManager) context.getSystemService(Context.RESTRICTIONS_SERVICE); + if (restrictionsManager == null) { + Log.d(TAG, "RestrictionsManager not available"); + return null; + } + + Bundle restrictions = restrictionsManager.getApplicationRestrictions(); + if (restrictions == null || restrictions.isEmpty()) { + Log.d(TAG, "No managed configuration found"); + return null; + } + + ManagedConfig config = Android.newManagedConfig(); + + String managementUrl = restrictions.getString( + Android.getManagedConfigKeyManagementURL(), ""); + if (!managementUrl.isEmpty()) { + config.setManagementURL(managementUrl); + Log.i(TAG, "MDM: management URL configured"); + } + + String setupKey = restrictions.getString( + Android.getManagedConfigKeySetupKey(), ""); + if (!setupKey.isEmpty()) { + config.setSetupKey(setupKey); + // Do not log the setup key value for security + Log.i(TAG, "MDM: setup key configured"); + } + + String adminUrl = restrictions.getString( + Android.getManagedConfigKeyAdminURL(), ""); + if (!adminUrl.isEmpty()) { + config.setAdminURL(adminUrl); + Log.i(TAG, "MDM: admin URL configured"); + } + + String preSharedKey = restrictions.getString( + Android.getManagedConfigKeyPreSharedKey(), ""); + if (!preSharedKey.isEmpty()) { + config.setPreSharedKey(preSharedKey); + Log.i(TAG, "MDM: pre-shared key configured"); + } + + if (restrictions.containsKey(Android.getManagedConfigKeyRosenpassEnabled())) { + boolean rosenpassEnabled = restrictions.getBoolean( + Android.getManagedConfigKeyRosenpassEnabled(), false); + config.setRosenpassEnabled(rosenpassEnabled); + Log.i(TAG, "MDM: Rosenpass enabled=" + rosenpassEnabled); + } + + if (restrictions.containsKey(Android.getManagedConfigKeyRosenpassPermissive())) { + boolean rosenpassPermissive = restrictions.getBoolean( + Android.getManagedConfigKeyRosenpassPermissive(), false); + config.setRosenpassPermissive(rosenpassPermissive); + Log.i(TAG, "MDM: Rosenpass permissive=" + rosenpassPermissive); + } + + if (restrictions.containsKey(Android.getManagedConfigKeyDisableAutoConnect())) { + boolean disableAutoConnect = restrictions.getBoolean( + Android.getManagedConfigKeyDisableAutoConnect(), false); + config.setDisableAutoConnect(disableAutoConnect); + Log.i(TAG, "MDM: disable auto-connect=" + disableAutoConnect); + } + + if (!config.hasConfig()) { + Log.d(TAG, "MDM restrictions present but no NetBird keys configured"); + return null; + } + + Log.i(TAG, "MDM managed configuration loaded successfully"); + return config; + } + + /** + * Returns true if any MDM-managed configuration is available for this app. + * + * @param context Android context + * @return true if managed config has values + */ + public static boolean hasManagedConfig(Context context) { + ManagedConfig config = read(context); + return config != null && config.hasConfig(); + } +} diff --git a/tool/src/main/java/io/netbird/client/tool/ManagedConfigReceiver.java b/tool/src/main/java/io/netbird/client/tool/ManagedConfigReceiver.java new file mode 100644 index 00000000..5d3ffaae --- /dev/null +++ b/tool/src/main/java/io/netbird/client/tool/ManagedConfigReceiver.java @@ -0,0 +1,58 @@ +package io.netbird.client.tool; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import io.netbird.gomobile.android.ManagedConfig; + +/** + * Receives {@code Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED} broadcasts + * when the MDM/EMM pushes updated managed configuration to the device. + * + *This receiver re-reads the managed configuration and applies it to the + * Go SDK config file. Work is performed off the main thread via {@code goAsync()} + * to avoid ANRs.
+ * + *Register this receiver in AndroidManifest.xml or dynamically in the VPNService.
+ */ +public class ManagedConfigReceiver extends BroadcastReceiver { + + private static final String TAG = "ManagedConfigReceiver"; + private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(); + + @Override + public void onReceive(Context context, Intent intent) { + if (!Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED.equals(intent.getAction())) { + return; + } + + Log.i(TAG, "Application restrictions changed, re-reading MDM config"); + + final PendingResult pendingResult = goAsync(); + EXECUTOR.execute(() -> { + try { + synchronized (EngineRunner.MDM_CONFIG_LOCK) { + ManagedConfig config = ManagedConfigReader.read(context); + if (config == null || !config.hasConfig()) { + Log.d(TAG, "No MDM config after restrictions change"); + return; + } + + ProfileManagerWrapper profileManager = new ProfileManagerWrapper(context); + String configPath = profileManager.getActiveConfigPath(); + config.apply(configPath); + Log.i(TAG, "MDM config re-applied after restrictions change"); + } + } catch (Exception e) { + Log.e(TAG, "Failed to apply MDM config after restrictions change", e); + } finally { + pendingResult.finish(); + } + }); + } +}