Implement always-on VPN separate lockdown setting
Allows Settings to control whether always-on VPN is required for the connection to complete, or if it is offered on a best-efforts basis. Change-Id: I5eb273a99e7559adc66b05e647c9130a819f99d4 Test: runtest -x tests/app/src/com/android/settings/vpn2/VpnTests.java Fix: 32420810
This commit is contained in:
@@ -5394,6 +5394,12 @@
|
||||
<string name="vpn_always_on_inactive">Always-on inactive</string>
|
||||
<!-- Preference summary for app not supporting always-on vpn [CHAR LIMIT=NONE] -->
|
||||
<string name="vpn_not_supported_by_this_app">Not supported by this app</string>
|
||||
<!-- Preference title for forcing all network connections to go through VPN. -->
|
||||
<string name="vpn_require_connection">Only allow connections through VPN</string>
|
||||
<!-- Preference summary for network connections being forced to go through VPN. -->
|
||||
<string name="vpn_lockdown_active">Lockdown active</string>
|
||||
<!-- Preference summary for network connections not being forced to go through VPN. -->
|
||||
<string name="vpn_lockdown_inactive">Lockdown inactive</string>
|
||||
|
||||
<!-- Summary describing the always-on VPN feature. [CHAR LIMIT=NONE] -->
|
||||
<string name="vpn_lockdown_summary">Select a VPN profile to always remain connected to. Network traffic will only be allowed when connected to this VPN.</string>
|
||||
|
@@ -33,6 +33,17 @@
|
||||
settings:useAdditionalSummary="true"
|
||||
settings:restrictedSwitchSummary="@string/disabled_by_admin_summary_text" />
|
||||
|
||||
<com.android.settingslib.RestrictedSwitchPreference
|
||||
android:key="lockdown_vpn"
|
||||
android:title="@string/vpn_require_connection"
|
||||
android:defaultValue="false"
|
||||
android:summaryOn="@string/vpn_lockdown_active"
|
||||
android:summaryOff="@string/vpn_lockdown_inactive"
|
||||
android:dependency="always_on_vpn"
|
||||
settings:userRestriction="no_config_vpn"
|
||||
settings:useAdditionalSummary="true"
|
||||
settings:restrictedSwitchSummary="@string/disabled_by_admin_summary_text" />
|
||||
|
||||
<com.android.settings.DimmableIconPreference
|
||||
android:key="forget_vpn"
|
||||
android:title="@string/vpn_forget_long"
|
||||
|
@@ -33,7 +33,6 @@ import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
@@ -63,6 +62,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
|
||||
private static final String KEY_VERSION = "version";
|
||||
private static final String KEY_ALWAYS_ON_VPN = "always_on_vpn";
|
||||
private static final String KEY_LOCKDOWN_VPN = "lockdown_vpn";
|
||||
private static final String KEY_FORGET_VPN = "forget_vpn";
|
||||
|
||||
private PackageManager mPackageManager;
|
||||
@@ -78,6 +78,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
// UI preference
|
||||
private Preference mPreferenceVersion;
|
||||
private RestrictedSwitchPreference mPreferenceAlwaysOn;
|
||||
private RestrictedSwitchPreference mPreferenceLockdown;
|
||||
private RestrictedPreference mPreferenceForget;
|
||||
|
||||
// Listener
|
||||
@@ -87,7 +88,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
public void onForget() {
|
||||
// Unset always-on-vpn when forgetting the VPN
|
||||
if (isVpnAlwaysOn()) {
|
||||
setAlwaysOnVpn(false);
|
||||
setAlwaysOnVpn(false, false);
|
||||
}
|
||||
// Also dismiss and go back to VPN list
|
||||
finish();
|
||||
@@ -118,9 +119,11 @@ public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
|
||||
mPreferenceVersion = findPreference(KEY_VERSION);
|
||||
mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN);
|
||||
mPreferenceLockdown = (RestrictedSwitchPreference) findPreference(KEY_LOCKDOWN_VPN);
|
||||
mPreferenceForget = (RestrictedPreference) findPreference(KEY_FORGET_VPN);
|
||||
|
||||
mPreferenceAlwaysOn.setOnPreferenceChangeListener(this);
|
||||
mPreferenceLockdown.setOnPreferenceChangeListener(this);
|
||||
mPreferenceForget.setOnPreferenceClickListener(this);
|
||||
}
|
||||
|
||||
@@ -154,7 +157,9 @@ public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
switch (preference.getKey()) {
|
||||
case KEY_ALWAYS_ON_VPN:
|
||||
return onAlwaysOnVpnClick((Boolean) newValue);
|
||||
return onAlwaysOnVpnClick((Boolean) newValue, mPreferenceLockdown.isChecked());
|
||||
case KEY_LOCKDOWN_VPN:
|
||||
return onAlwaysOnVpnClick(mPreferenceAlwaysOn.isChecked(), (Boolean) newValue);
|
||||
default:
|
||||
Log.w(TAG, "unknown key is clicked: " + preference.getKey());
|
||||
return false;
|
||||
@@ -176,27 +181,28 @@ public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean onAlwaysOnVpnClick(final boolean alwaysOnSetting) {
|
||||
private boolean onAlwaysOnVpnClick(final boolean alwaysOnSetting, final boolean lockdown) {
|
||||
final boolean replacing = isAnotherVpnActive();
|
||||
final boolean wasAlwaysOn = VpnUtils.isAlwaysOnOrLegacyLockdownActive(getActivity());
|
||||
if (ConfirmLockdownFragment.shouldShow(replacing, wasAlwaysOn, alwaysOnSetting)) {
|
||||
final boolean wasLockdown = VpnUtils.isAnyLockdownActive(getActivity());
|
||||
if (ConfirmLockdownFragment.shouldShow(replacing, wasLockdown, lockdown)) {
|
||||
// Place a dialog to confirm that traffic should be locked down.
|
||||
final Bundle options = null;
|
||||
ConfirmLockdownFragment.show(this, replacing, wasAlwaysOn, alwaysOnSetting, options);
|
||||
ConfirmLockdownFragment.show(
|
||||
this, replacing, alwaysOnSetting, wasLockdown, lockdown, options);
|
||||
return false;
|
||||
}
|
||||
// No need to show the dialog. Change the setting straight away.
|
||||
return setAlwaysOnVpnByUI(alwaysOnSetting);
|
||||
return setAlwaysOnVpnByUI(alwaysOnSetting, lockdown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfirmLockdown(Bundle options, boolean isEnabled) {
|
||||
if (setAlwaysOnVpnByUI(isEnabled)) {
|
||||
public void onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown) {
|
||||
if (setAlwaysOnVpnByUI(isEnabled, isLockdown)) {
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setAlwaysOnVpnByUI(boolean isEnabled) {
|
||||
private boolean setAlwaysOnVpnByUI(boolean isEnabled, boolean isLockdown) {
|
||||
updateRestrictedViews();
|
||||
if (!mPreferenceAlwaysOn.isEnabled()) {
|
||||
return false;
|
||||
@@ -205,16 +211,16 @@ public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
if (mUserId == UserHandle.USER_SYSTEM) {
|
||||
VpnUtils.clearLockdownVpn(getContext());
|
||||
}
|
||||
final boolean success = setAlwaysOnVpn(isEnabled);
|
||||
final boolean success = setAlwaysOnVpn(isEnabled, isLockdown);
|
||||
if (isEnabled && (!success || !isVpnAlwaysOn())) {
|
||||
CannotConnectFragment.show(this, mVpnLabel);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean setAlwaysOnVpn(boolean isEnabled) {
|
||||
return mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId,
|
||||
isEnabled ? mPackageName : null, /* lockdownEnabled */ true);
|
||||
private boolean setAlwaysOnVpn(boolean isEnabled, boolean isLockdown) {
|
||||
return mConnectivityManager.setAlwaysOnVpnPackageForUser(mUserId,
|
||||
isEnabled ? mPackageName : null, isLockdown);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -232,7 +238,12 @@ public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
|
||||
private void updateUI() {
|
||||
if (isAdded()) {
|
||||
mPreferenceAlwaysOn.setChecked(isVpnAlwaysOn());
|
||||
final boolean alwaysOn = isVpnAlwaysOn();
|
||||
final boolean lockdown = alwaysOn
|
||||
&& VpnUtils.isAnyLockdownActive(getActivity());
|
||||
|
||||
mPreferenceAlwaysOn.setChecked(alwaysOn);
|
||||
mPreferenceLockdown.setChecked(lockdown);
|
||||
updateRestrictedViews();
|
||||
}
|
||||
}
|
||||
@@ -241,6 +252,8 @@ public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
if (isAdded()) {
|
||||
mPreferenceAlwaysOn.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
|
||||
mUserId);
|
||||
mPreferenceLockdown.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
|
||||
mUserId);
|
||||
mPreferenceForget.checkRestrictionAndSetDisabled(UserManager.DISALLOW_CONFIG_VPN,
|
||||
mUserId);
|
||||
|
||||
@@ -251,6 +264,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment
|
||||
// should have refreshed the enable state.
|
||||
} else {
|
||||
mPreferenceAlwaysOn.setEnabled(false);
|
||||
mPreferenceLockdown.setEnabled(false);
|
||||
mPreferenceAlwaysOn.setSummary(R.string.vpn_not_supported_by_this_app);
|
||||
}
|
||||
}
|
||||
|
@@ -134,9 +134,9 @@ public class ConfigDialogFragment extends InstrumentedDialogFragment implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfirmLockdown(Bundle options, boolean isEnabled) {
|
||||
public void onConfirmLockdown(Bundle options, boolean isAlwaysOn, boolean isLockdown) {
|
||||
VpnProfile profile = (VpnProfile) options.getParcelable(ARG_PROFILE);
|
||||
connect(profile, isEnabled);
|
||||
connect(profile, isAlwaysOn);
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@@ -149,14 +149,15 @@ public class ConfigDialogFragment extends InstrumentedDialogFragment implements
|
||||
// Possibly throw up a dialog to explain lockdown VPN.
|
||||
final boolean shouldLockdown = dialog.isVpnAlwaysOn();
|
||||
final boolean shouldConnect = shouldLockdown || !dialog.isEditing();
|
||||
final boolean wasAlwaysOn = VpnUtils.isAlwaysOnOrLegacyLockdownActive(mContext);
|
||||
final boolean wasLockdown = VpnUtils.isAnyLockdownActive(mContext);
|
||||
try {
|
||||
final boolean replace = VpnUtils.isVpnActive(mContext);
|
||||
if (shouldConnect && !isConnected(profile) &&
|
||||
ConfirmLockdownFragment.shouldShow(replace, wasAlwaysOn, shouldLockdown)) {
|
||||
ConfirmLockdownFragment.shouldShow(replace, wasLockdown, shouldLockdown)) {
|
||||
final Bundle opts = new Bundle();
|
||||
opts.putParcelable(ARG_PROFILE, profile);
|
||||
ConfirmLockdownFragment.show(this, replace, wasAlwaysOn, shouldLockdown, opts);
|
||||
ConfirmLockdownFragment.show(this, replace, /* alwaysOn */ shouldLockdown,
|
||||
/* from */ wasLockdown, /* to */ shouldLockdown, opts);
|
||||
} else if (shouldConnect) {
|
||||
connect(profile, shouldLockdown);
|
||||
} else {
|
||||
|
@@ -29,7 +29,7 @@ import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
public class ConfirmLockdownFragment extends InstrumentedDialogFragment
|
||||
implements DialogInterface.OnClickListener {
|
||||
public interface ConfirmLockdownListener {
|
||||
public void onConfirmLockdown(Bundle options, boolean isEnabled);
|
||||
public void onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown);
|
||||
}
|
||||
|
||||
private static final String TAG = "ConfirmLockdown";
|
||||
@@ -40,6 +40,7 @@ public class ConfirmLockdownFragment extends InstrumentedDialogFragment
|
||||
}
|
||||
|
||||
private static final String ARG_REPLACING = "replacing";
|
||||
private static final String ARG_ALWAYS_ON = "always_on";
|
||||
private static final String ARG_LOCKDOWN_SRC = "lockdown_old";
|
||||
private static final String ARG_LOCKDOWN_DST = "lockdown_new";
|
||||
private static final String ARG_OPTIONS = "options";
|
||||
@@ -47,11 +48,11 @@ public class ConfirmLockdownFragment extends InstrumentedDialogFragment
|
||||
public static boolean shouldShow(boolean replacing, boolean fromLockdown, boolean toLockdown) {
|
||||
// We only need to show this if we are:
|
||||
// - replacing an existing connection
|
||||
// - switching on always-on mode where it was not enabled before.
|
||||
// - switching on always-on mode with lockdown enabled where it was not enabled before.
|
||||
return replacing || (toLockdown && !fromLockdown);
|
||||
}
|
||||
|
||||
public static void show(Fragment parent, boolean replacing,
|
||||
public static void show(Fragment parent, boolean replacing, boolean alwaysOn,
|
||||
boolean fromLockdown, boolean toLockdown, Bundle options) {
|
||||
if (parent.getFragmentManager().findFragmentByTag(TAG) != null) {
|
||||
// Already exists. Don't show it twice.
|
||||
@@ -59,6 +60,7 @@ public class ConfirmLockdownFragment extends InstrumentedDialogFragment
|
||||
}
|
||||
final Bundle args = new Bundle();
|
||||
args.putBoolean(ARG_REPLACING, replacing);
|
||||
args.putBoolean(ARG_ALWAYS_ON, alwaysOn);
|
||||
args.putBoolean(ARG_LOCKDOWN_SRC, fromLockdown);
|
||||
args.putBoolean(ARG_LOCKDOWN_DST, toLockdown);
|
||||
args.putParcelable(ARG_OPTIONS, options);
|
||||
@@ -72,20 +74,21 @@ public class ConfirmLockdownFragment extends InstrumentedDialogFragment
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final boolean replacing = getArguments().getBoolean(ARG_REPLACING);
|
||||
final boolean wasAlwaysOn = getArguments().getBoolean(ARG_LOCKDOWN_SRC);
|
||||
final boolean nowAlwaysOn = getArguments().getBoolean(ARG_LOCKDOWN_DST);
|
||||
final boolean alwaysOn = getArguments().getBoolean(ARG_ALWAYS_ON);
|
||||
final boolean wasLockdown = getArguments().getBoolean(ARG_LOCKDOWN_SRC);
|
||||
final boolean nowLockdown = getArguments().getBoolean(ARG_LOCKDOWN_DST);
|
||||
|
||||
final int titleId = replacing ? R.string.vpn_replace_vpn_title : R.string.vpn_set_vpn_title;
|
||||
final int actionId =
|
||||
(replacing ? R.string.vpn_replace :
|
||||
(nowAlwaysOn ? R.string.vpn_turn_on : R.string.okay));
|
||||
(nowLockdown ? R.string.vpn_turn_on : R.string.okay));
|
||||
final int messageId;
|
||||
if (nowAlwaysOn) {
|
||||
if (nowLockdown) {
|
||||
messageId = replacing
|
||||
? R.string.vpn_replace_always_on_vpn_enable_message
|
||||
: R.string.vpn_first_always_on_vpn_message;
|
||||
} else {
|
||||
messageId = wasAlwaysOn
|
||||
messageId = wasLockdown
|
||||
? R.string.vpn_replace_always_on_vpn_disable_message
|
||||
: R.string.vpn_replace_vpn_message;
|
||||
}
|
||||
@@ -103,6 +106,7 @@ public class ConfirmLockdownFragment extends InstrumentedDialogFragment
|
||||
if (getTargetFragment() instanceof ConfirmLockdownListener) {
|
||||
((ConfirmLockdownListener) getTargetFragment()).onConfirmLockdown(
|
||||
getArguments().getParcelable(ARG_OPTIONS),
|
||||
getArguments().getBoolean(ARG_ALWAYS_ON),
|
||||
getArguments().getBoolean(ARG_LOCKDOWN_DST));
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import android.net.ConnectivityManager;
|
||||
import android.net.IConnectivityManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.provider.Settings;
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
|
||||
@@ -53,10 +54,14 @@ public class VpnUtils {
|
||||
return key.equals(getLockdownVpn());
|
||||
}
|
||||
|
||||
public static boolean isAlwaysOnOrLegacyLockdownActive(Context context) {
|
||||
public static boolean isAnyLockdownActive(Context context) {
|
||||
final int userId = context.getUserId();
|
||||
return getLockdownVpn() != null
|
||||
|| getConnectivityManager(context).getAlwaysOnVpnPackageForUser(userId) != null;
|
||||
if (getLockdownVpn() != null) {
|
||||
return true;
|
||||
}
|
||||
return getConnectivityManager(context).getAlwaysOnVpnPackageForUser(userId) != null
|
||||
&& Settings.Secure.getIntForUser(context.getContentResolver(),
|
||||
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN, /* default */ 0, userId) != 0;
|
||||
}
|
||||
|
||||
public static boolean isVpnActive(Context context) throws RemoteException {
|
||||
|
Reference in New Issue
Block a user