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
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4d872b1..9e5176c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -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>
diff --git a/res/xml/vpn_app_management.xml b/res/xml/vpn_app_management.xml
index 93e15bc..1b6f37b 100644
--- a/res/xml/vpn_app_management.xml
+++ b/res/xml/vpn_app_management.xml
@@ -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"
diff --git a/src/com/android/settings/vpn2/AppManagementFragment.java b/src/com/android/settings/vpn2/AppManagementFragment.java
index 23901ce..f86e677 100644
--- a/src/com/android/settings/vpn2/AppManagementFragment.java
+++ b/src/com/android/settings/vpn2/AppManagementFragment.java
@@ -33,7 +33,6 @@
 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 @@
 
     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 @@
     // UI preference
     private Preference mPreferenceVersion;
     private RestrictedSwitchPreference mPreferenceAlwaysOn;
+    private RestrictedSwitchPreference mPreferenceLockdown;
     private RestrictedPreference mPreferenceForget;
 
     // Listener
@@ -87,7 +88,7 @@
         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 @@
 
         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 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 @@
         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 @@
         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 @@
 
     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 @@
         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 @@
                 // should have refreshed the enable state.
             } else {
                 mPreferenceAlwaysOn.setEnabled(false);
+                mPreferenceLockdown.setEnabled(false);
                 mPreferenceAlwaysOn.setSummary(R.string.vpn_not_supported_by_this_app);
             }
         }
diff --git a/src/com/android/settings/vpn2/ConfigDialogFragment.java b/src/com/android/settings/vpn2/ConfigDialogFragment.java
index d9f35af..cf748e4 100644
--- a/src/com/android/settings/vpn2/ConfigDialogFragment.java
+++ b/src/com/android/settings/vpn2/ConfigDialogFragment.java
@@ -134,9 +134,9 @@
     }
 
     @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 @@
             // 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 {
diff --git a/src/com/android/settings/vpn2/ConfirmLockdownFragment.java b/src/com/android/settings/vpn2/ConfirmLockdownFragment.java
index 246c2f2..0d40e4c 100644
--- a/src/com/android/settings/vpn2/ConfirmLockdownFragment.java
+++ b/src/com/android/settings/vpn2/ConfirmLockdownFragment.java
@@ -29,7 +29,7 @@
 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 @@
     }
 
     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 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 @@
         }
         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 @@
     @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 @@
         if (getTargetFragment() instanceof ConfirmLockdownListener) {
             ((ConfirmLockdownListener) getTargetFragment()).onConfirmLockdown(
                     getArguments().getParcelable(ARG_OPTIONS),
+                    getArguments().getBoolean(ARG_ALWAYS_ON),
                     getArguments().getBoolean(ARG_LOCKDOWN_DST));
         }
     }
diff --git a/src/com/android/settings/vpn2/VpnUtils.java b/src/com/android/settings/vpn2/VpnUtils.java
index 5990381..07e6c52 100644
--- a/src/com/android/settings/vpn2/VpnUtils.java
+++ b/src/com/android/settings/vpn2/VpnUtils.java
@@ -20,6 +20,7 @@
 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 @@
         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 {