Merge "Query HTTP proxy for network via a new API to avoid permissions exceptions" into mnc-dev
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 1c67f31..e175e9a 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -496,6 +496,8 @@
      * Tests if a given integer represents a valid network type.
      * @param networkType the type to be tested
      * @return a boolean.  {@code true} if the type is valid, else {@code false}
+     * @deprecated All APIs accepting a network type are deprecated. There should be no need to
+     *             validate a network type.
      */
     public static boolean isNetworkTypeValid(int networkType) {
         return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 397e47b..89d23a2 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -106,13 +106,13 @@
 
     ProxyInfo getProxyForNetwork(in Network nework);
 
-    boolean prepareVpn(String oldPackage, String newPackage);
+    boolean prepareVpn(String oldPackage, String newPackage, int userId);
 
-    void setVpnPackageAuthorization(boolean authorized);
+    void setVpnPackageAuthorization(String packageName, int userId, boolean authorized);
 
     ParcelFileDescriptor establishVpn(in VpnConfig config);
 
-    VpnConfig getVpnConfig();
+    VpnConfig getVpnConfig(int userId);
 
     void startLegacyVpn(in VpnProfile profile);
 
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index ab70485..cf747cf 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -619,6 +619,7 @@
                 case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break;
                 case NET_CAPABILITY_TRUSTED:        capabilities += "TRUSTED"; break;
                 case NET_CAPABILITY_NOT_VPN:        capabilities += "NOT_VPN"; break;
+                case NET_CAPABILITY_VALIDATED:      capabilities += "VALIDATED"; break;
             }
             if (++i < types.length) capabilities += "&";
         }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index eceac51..7ac2655 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -23,6 +23,10 @@
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
@@ -190,7 +194,7 @@
     /** Set of ifaces that are costly. */
     private HashSet<String> mMeteredIfaces = Sets.newHashSet();
 
-    private Context mContext;
+    final private Context mContext;
     private int mNetworkPreference;
     // 0 is full bad, 100 is full good
     private int mDefaultInetConditionPublished = 0;
@@ -344,6 +348,11 @@
      */
     private static final int EVENT_PROMPT_UNVALIDATED = 29;
 
+    /**
+     * used internally to (re)configure mobile data always-on settings.
+     */
+    private static final int EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON = 30;
+
     /** Handler used for internal events. */
     final private InternalHandler mHandler;
     /** Handler used for incoming {@link NetworkStateTracker} events. */
@@ -374,7 +383,7 @@
 
     private PacManager mPacManager = null;
 
-    private SettingsObserver mSettingsObserver;
+    final private SettingsObserver mSettingsObserver;
 
     private UserManager mUserManager;
 
@@ -458,11 +467,12 @@
             }
         }
 
-        private void maybeLogBroadcast(NetworkAgentInfo nai, boolean connected, int type) {
+        private void maybeLogBroadcast(NetworkAgentInfo nai, boolean connected, int type,
+                boolean isDefaultNetwork) {
             if (DBG) {
                 log("Sending " + (connected ? "connected" : "disconnected") +
                         " broadcast for type " + type + " " + nai.name() +
-                        " isDefaultNetwork=" + isDefaultNetwork(nai));
+                        " isDefaultNetwork=" + isDefaultNetwork);
             }
         }
 
@@ -482,43 +492,45 @@
             list.add(nai);
 
             // Send a broadcast if this is the first network of its type or if it's the default.
-            if (list.size() == 1 || isDefaultNetwork(nai)) {
-                maybeLogBroadcast(nai, true, type);
+            final boolean isDefaultNetwork = isDefaultNetwork(nai);
+            if (list.size() == 1 || isDefaultNetwork) {
+                maybeLogBroadcast(nai, true, type, isDefaultNetwork);
                 sendLegacyNetworkBroadcast(nai, true, type);
             }
         }
 
         /** Removes the given network from the specified legacy type list. */
-        public void remove(int type, NetworkAgentInfo nai) {
+        public void remove(int type, NetworkAgentInfo nai, boolean wasDefault) {
             ArrayList<NetworkAgentInfo> list = mTypeLists[type];
             if (list == null || list.isEmpty()) {
                 return;
             }
 
-            boolean wasFirstNetwork = list.get(0).equals(nai);
+            final boolean wasFirstNetwork = list.get(0).equals(nai);
 
             if (!list.remove(nai)) {
                 return;
             }
 
-            if (wasFirstNetwork || isDefaultNetwork(nai)) {
-                maybeLogBroadcast(nai, false, type);
+            if (wasFirstNetwork || wasDefault) {
+                maybeLogBroadcast(nai, false, type, wasDefault);
                 sendLegacyNetworkBroadcast(nai, false, type);
             }
 
             if (!list.isEmpty() && wasFirstNetwork) {
                 if (DBG) log("Other network available for type " + type +
                               ", sending connected broadcast");
-                maybeLogBroadcast(list.get(0), false, type);
-                sendLegacyNetworkBroadcast(list.get(0), false, type);
+                final NetworkAgentInfo replacement = list.get(0);
+                maybeLogBroadcast(replacement, false, type, isDefaultNetwork(replacement));
+                sendLegacyNetworkBroadcast(replacement, false, type);
             }
         }
 
         /** Removes the given network from all legacy type lists. */
-        public void remove(NetworkAgentInfo nai) {
-            if (VDBG) log("Removing agent " + nai);
+        public void remove(NetworkAgentInfo nai, boolean wasDefault) {
+            if (VDBG) log("Removing agent " + nai + " wasDefault=" + wasDefault);
             for (int type = 0; type < mTypeLists.length; type++) {
-                remove(type, nai);
+                remove(type, nai, wasDefault);
             }
         }
 
@@ -555,13 +567,12 @@
             INetworkStatsService statsService, INetworkPolicyManager policyManager) {
         if (DBG) log("ConnectivityService starting up");
 
-        NetworkCapabilities netCap = new NetworkCapabilities();
-        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
-        mDefaultRequest = new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId());
-        NetworkRequestInfo nri = new NetworkRequestInfo(null, mDefaultRequest, new Binder(),
-                NetworkRequestInfo.REQUEST);
-        mNetworkRequests.put(mDefaultRequest, nri);
+        mDefaultRequest = createInternetRequestForTransport(-1);
+        mNetworkRequests.put(mDefaultRequest, new NetworkRequestInfo(
+                null, mDefaultRequest, new Binder(), NetworkRequestInfo.REQUEST));
+
+        mDefaultMobileDataRequest = createInternetRequestForTransport(
+                NetworkCapabilities.TRANSPORT_CELLULAR);
 
         HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
         handlerThread.start();
@@ -696,8 +707,8 @@
             mInetLog = new ArrayList();
         }
 
-        mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY);
-        mSettingsObserver.observe(mContext);
+        mSettingsObserver = new SettingsObserver(mContext, mHandler);
+        registerSettingsCallbacks();
 
         mDataConnectionStats = new DataConnectionStats(mContext);
         mDataConnectionStats.startMonitoring();
@@ -707,6 +718,44 @@
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
     }
 
+    private NetworkRequest createInternetRequestForTransport(int transportType) {
+        NetworkCapabilities netCap = new NetworkCapabilities();
+        netCap.addCapability(NET_CAPABILITY_INTERNET);
+        netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        if (transportType > -1) {
+            netCap.addTransportType(transportType);
+        }
+        return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId());
+    }
+
+    private void handleMobileDataAlwaysOn() {
+        final boolean enable = (Settings.Global.getInt(
+                mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 0) == 1);
+        final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null);
+        if (enable == isEnabled) {
+            return;  // Nothing to do.
+        }
+
+        if (enable) {
+            handleRegisterNetworkRequest(new NetworkRequestInfo(
+                    null, mDefaultMobileDataRequest, new Binder(), NetworkRequestInfo.REQUEST));
+        } else {
+            handleReleaseNetworkRequest(mDefaultMobileDataRequest, Process.SYSTEM_UID);
+        }
+    }
+
+    private void registerSettingsCallbacks() {
+        // Watch for global HTTP proxy changes.
+        mSettingsObserver.observe(
+                Settings.Global.getUriFor(Settings.Global.HTTP_PROXY),
+                EVENT_APPLY_GLOBAL_HTTP_PROXY);
+
+        // Watch for whether or not to keep mobile data always on.
+        mSettingsObserver.observe(
+                Settings.Global.getUriFor(Settings.Global.MOBILE_DATA_ALWAYS_ON),
+                EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON);
+    }
+
     private synchronized int nextNetworkRequestId() {
         return mNextNetworkRequestId++;
     }
@@ -1022,23 +1071,6 @@
         }
     }
 
-    private NetworkCapabilities getNetworkCapabilitiesAndValidation(NetworkAgentInfo nai) {
-        if (nai != null) {
-            synchronized (nai) {
-                if (nai.created) {
-                    NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
-                    if (nai.lastValidated) {
-                        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
-                    } else {
-                        nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
-                    }
-                    return nc;
-                }
-            }
-        }
-        return null;
-    }
-
     @Override
     public NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId) {
         // The basic principle is: if an app's traffic could possibly go over a
@@ -1060,7 +1092,7 @@
         HashMap<Network, NetworkCapabilities> result = new HashMap<Network, NetworkCapabilities>();
 
         NetworkAgentInfo nai = getDefaultNetwork();
-        NetworkCapabilities nc = getNetworkCapabilitiesAndValidation(getDefaultNetwork());
+        NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
         if (nc != null) {
             result.put(nai.network, nc);
         }
@@ -1073,9 +1105,9 @@
                     if (networks != null) {
                         for (Network network : networks) {
                             nai = getNetworkAgentInfoForNetwork(network);
-                            nc = getNetworkCapabilitiesAndValidation(nai);
+                            nc = getNetworkCapabilitiesInternal(nai);
                             if (nc != null) {
-                                result.put(nai.network, nc);
+                                result.put(network, nc);
                             }
                         }
                     }
@@ -1135,25 +1167,24 @@
         return null;
     }
 
-    @Override
-    public NetworkCapabilities getNetworkCapabilities(Network network) {
-        enforceAccessPermission();
-        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+    private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
         if (nai != null) {
             synchronized (nai) {
-                NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
-                if (nai.lastValidated) {
-                    nc.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
-                } else {
-                    nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+                if (nai.networkCapabilities != null) {
+                    return new NetworkCapabilities(nai.networkCapabilities);
                 }
-                return nc;
             }
         }
         return null;
     }
 
     @Override
+    public NetworkCapabilities getNetworkCapabilities(Network network) {
+        enforceAccessPermission();
+        return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
+    }
+
+    @Override
     public NetworkState[] getAllNetworkState() {
         // Require internal since we're handing out IMSI details
         enforceConnectivityInternalPermission();
@@ -1361,6 +1392,22 @@
         }
     };
 
+    /**
+     * Require that the caller is either in the same user or has appropriate permission to interact
+     * across users.
+     *
+     * @param userId Target user for whatever operation the current IPC is supposed to perform.
+     */
+    private void enforceCrossUserPermission(int userId) {
+        if (userId == UserHandle.getCallingUserId()) {
+            // Not a cross-user call.
+            return;
+        }
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                "ConnectivityService");
+    }
+
     private void enforceInternetPermission() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.INTERNET,
@@ -1491,6 +1538,9 @@
             mContext.registerReceiver(mUserPresentReceiver, filter);
         }
 
+        // Configure whether mobile data is always on.
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON));
+
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_SYSTEM_READY));
 
         mPermissionMonitor.startMonitoring();
@@ -1898,11 +1948,14 @@
                 }
                 case NetworkMonitor.EVENT_NETWORK_TESTED: {
                     NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
-                    if (isLiveNetworkAgent(nai, "EVENT_NETWORK_VALIDATED")) {
-                        boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
+                    if (isLiveNetworkAgent(nai, "EVENT_NETWORK_TESTED")) {
+                        final boolean valid =
+                                (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
+                        final boolean validationChanged = (valid != nai.lastValidated);
                         nai.lastValidated = valid;
                         if (valid) {
                             if (DBG) log("Validated " + nai.name());
+                            nai.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
                             if (!nai.everValidated) {
                                 nai.everValidated = true;
                                 rematchNetworkAndRequests(nai, NascentState.JUST_VALIDATED,
@@ -1910,6 +1963,8 @@
                                 // If score has changed, rebroadcast to NetworkFactories. b/17726566
                                 sendUpdatedScoreToFactories(nai);
                             }
+                        } else {
+                            nai.networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED);
                         }
                         updateInetCondition(nai);
                         // Let the NetworkAgent know the state of its network
@@ -1918,8 +1973,9 @@
                                 (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
                                 0, null);
 
-                        // TODO: trigger a NetworkCapabilities update so that the dialog can know
-                        // that the network is now validated and close itself.
+                        if (validationChanged) {
+                            notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+                        }
                     }
                     break;
                 }
@@ -1934,7 +1990,7 @@
                     if (msg.arg1 == 0) {
                         setProvNotificationVisibleIntent(false, msg.arg2, 0, null, null);
                     } else {
-                        NetworkAgentInfo nai = null;
+                        final NetworkAgentInfo nai;
                         synchronized (mNetworkForNetId) {
                             nai = mNetworkForNetId.get(msg.arg2);
                         }
@@ -1942,6 +1998,7 @@
                             loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
                             break;
                         }
+                        nai.captivePortalDetected = true;
                         setProvNotificationVisibleIntent(true, msg.arg2, nai.networkInfo.getType(),
                                 nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj);
                     }
@@ -1991,12 +2048,13 @@
                 loge("Error connecting NetworkAgent");
                 NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo);
                 if (nai != null) {
+                    final boolean wasDefault = isDefaultNetwork(nai);
                     synchronized (mNetworkForNetId) {
                         mNetworkForNetId.remove(nai.network.netId);
                         mNetIdInUse.delete(nai.network.netId);
                     }
                     // Just in case.
-                    mLegacyTypeTracker.remove(nai);
+                    mLegacyTypeTracker.remove(nai, wasDefault);
                 }
             }
         }
@@ -2026,7 +2084,8 @@
                 nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
                         null, null);
             }
-            if (isDefaultNetwork(nai)) {
+            final boolean wasDefault = isDefaultNetwork(nai);
+            if (wasDefault) {
                 mDefaultInetConditionPublished = 0;
             }
             notifyIfacesChanged();
@@ -2034,7 +2093,6 @@
             nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
             mNetworkAgentInfos.remove(msg.replyTo);
             updateClat(null, nai.linkProperties, nai);
-            mLegacyTypeTracker.remove(nai);
             synchronized (mNetworkForNetId) {
                 mNetworkForNetId.remove(nai.network.netId);
                 mNetIdInUse.delete(nai.network.netId);
@@ -2073,6 +2131,7 @@
                 notifyLockdownVpn(nai);
                 requestNetworkTransitionWakelock(nai.name());
             }
+            mLegacyTypeTracker.remove(nai, wasDefault);
             for (NetworkAgentInfo networkToActivate : toActivate) {
                 unlinger(networkToActivate);
                 rematchNetworkAndRequests(networkToActivate, NascentState.NOT_JUST_VALIDATED,
@@ -2107,12 +2166,10 @@
                     + nri.request + " because their intents matched.");
             handleReleaseNetworkRequest(existingRequest.request, getCallingUid());
         }
-        handleRegisterNetworkRequest(msg);
+        handleRegisterNetworkRequest(nri);
     }
 
-    private void handleRegisterNetworkRequest(Message msg) {
-        final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
-
+    private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
         mNetworkRequests.put(nri.request, nri);
 
         // TODO: This logic may be better replaced with a call to rematchNetworkAndRequests
@@ -2264,7 +2321,7 @@
                     }
 
                     if (doRemove) {
-                        mLegacyTypeTracker.remove(nri.request.legacyType, nai);
+                        mLegacyTypeTracker.remove(nri.request.legacyType, nai, false);
                     }
                 }
 
@@ -2336,7 +2393,8 @@
 
         // Only prompt if the network is unvalidated and was explicitly selected by the user, and if
         // we haven't already been told to switch to it regardless of whether it validated or not.
-        if (nai == null || nai.everValidated ||
+        // Also don't prompt on captive portals because we're already prompting the user to sign in.
+        if (nai == null || nai.everValidated || nai.captivePortalDetected ||
                 !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
             return;
         }
@@ -2423,7 +2481,7 @@
                 }
                 case EVENT_REGISTER_NETWORK_REQUEST:
                 case EVENT_REGISTER_NETWORK_LISTENER: {
-                    handleRegisterNetworkRequest(msg);
+                    handleRegisterNetworkRequest((NetworkRequestInfo) msg.obj);
                     break;
                 }
                 case EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT: {
@@ -2446,6 +2504,10 @@
                     handlePromptUnvalidated((Network) msg.obj);
                     break;
                 }
+                case EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON: {
+                    handleMobileDataAlwaysOn();
+                    break;
+                }
                 case EVENT_SYSTEM_READY: {
                     for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
                         nai.networkMonitor.systemReady = true;
@@ -2853,23 +2915,36 @@
     }
 
     private static class SettingsObserver extends ContentObserver {
-        private int mWhat;
-        private Handler mHandler;
-        SettingsObserver(Handler handler, int what) {
-            super(handler);
+        final private HashMap<Uri, Integer> mUriEventMap;
+        final private Context mContext;
+        final private Handler mHandler;
+
+        SettingsObserver(Context context, Handler handler) {
+            super(null);
+            mUriEventMap = new HashMap<Uri, Integer>();
+            mContext = context;
             mHandler = handler;
-            mWhat = what;
         }
 
-        void observe(Context context) {
-            ContentResolver resolver = context.getContentResolver();
-            resolver.registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.HTTP_PROXY), false, this);
+        void observe(Uri uri, int what) {
+            mUriEventMap.put(uri, what);
+            final ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(uri, false, this);
         }
 
         @Override
         public void onChange(boolean selfChange) {
-            mHandler.obtainMessage(mWhat).sendToTarget();
+            Slog.wtf(TAG, "Should never be reached.");
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            final Integer what = mUriEventMap.get(uri);
+            if (what != null) {
+                mHandler.obtainMessage(what.intValue()).sendToTarget();
+            } else {
+                loge("No matching event to send for URI=" + uri);
+            }
         }
     }
 
@@ -2890,29 +2965,48 @@
 
     /**
      * Prepare for a VPN application.
-     * Permissions are checked in Vpn class.
+     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
+     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+     *
+     * @param oldPackage Package name of the application which currently controls VPN, which will
+     *                   be replaced. If there is no such application, this should should either be
+     *                   {@code null} or {@link VpnConfig.LEGACY_VPN}.
+     * @param newPackage Package name of the application which should gain control of VPN, or
+     *                   {@code null} to disable.
+     * @param userId User for whom to prepare the new VPN.
+     *
      * @hide
      */
     @Override
-    public boolean prepareVpn(String oldPackage, String newPackage) {
+    public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
+            int userId) {
+        enforceCrossUserPermission(userId);
         throwIfLockdownEnabled();
-        int user = UserHandle.getUserId(Binder.getCallingUid());
+
         synchronized(mVpns) {
-            return mVpns.get(user).prepare(oldPackage, newPackage);
+            return mVpns.get(userId).prepare(oldPackage, newPackage);
         }
     }
 
     /**
-     * Set whether the current VPN package has the ability to launch VPNs without
-     * user intervention. This method is used by system-privileged apps.
-     * Permissions are checked in Vpn class.
+     * Set whether the VPN package has the ability to launch VPNs without user intervention.
+     * This method is used by system-privileged apps.
+     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
+     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+     *
+     * @param packageName The package for which authorization state should change.
+     * @param userId User for whom {@code packageName} is installed.
+     * @param authorized {@code true} if this app should be able to start a VPN connection without
+     *                   explicit user approval, {@code false} if not.
+     *
      * @hide
      */
     @Override
-    public void setVpnPackageAuthorization(boolean authorized) {
-        int user = UserHandle.getUserId(Binder.getCallingUid());
+    public void setVpnPackageAuthorization(String packageName, int userId, boolean authorized) {
+        enforceCrossUserPermission(userId);
+
         synchronized(mVpns) {
-            mVpns.get(user).setPackageAuthorization(authorized);
+            mVpns.get(userId).setPackageAuthorization(packageName, authorized);
         }
     }
 
@@ -3014,16 +3108,16 @@
     }
 
     /**
-     * Returns the information of the ongoing VPN. This method is used by VpnDialogs and
-     * not available in ConnectivityManager.
+     * Returns the information of the ongoing VPN for {@code userId}. This method is used by
+     * VpnDialogs and not available in ConnectivityManager.
      * Permissions are checked in Vpn class.
      * @hide
      */
     @Override
-    public VpnConfig getVpnConfig() {
-        int user = UserHandle.getUserId(Binder.getCallingUid());
+    public VpnConfig getVpnConfig(int userId) {
+        enforceCrossUserPermission(userId);
         synchronized(mVpns) {
-            return mVpns.get(user).getVpnConfig();
+            return mVpns.get(userId).getVpnConfig();
         }
     }
 
@@ -3479,8 +3573,7 @@
     }
 
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities) {
-        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                == false) {
+        if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
             enforceConnectivityInternalPermission();
         } else {
             enforceChangePermission();
@@ -3507,8 +3600,7 @@
 
     private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) {
         // if UID is restricted, don't allow them to bring up metered APNs
-        if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
-                == false) {
+        if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) == false) {
             final int uidRules;
             final int uid = Binder.getCallingUid();
             synchronized(mRulesLock) {
@@ -3517,7 +3609,7 @@
             if ((uidRules & (RULE_REJECT_METERED | RULE_REJECT_ALL)) != 0) {
                 // we could silently fail or we can filter the available nets to only give
                 // them those they have access to.  Chose the more useful
-                networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+                networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
             }
         }
     }
@@ -3659,6 +3751,10 @@
     // Note: if mDefaultRequest is changed, NetworkMonitor needs to be updated.
     private final NetworkRequest mDefaultRequest;
 
+    // Request used to optionally keep mobile data active even when higher
+    // priority networks like Wi-Fi are active.
+    private final NetworkRequest mDefaultMobileDataRequest;
+
     private NetworkAgentInfo getDefaultNetwork() {
         return mNetworkForRequestId.get(mDefaultRequest.requestId);
     }
@@ -3719,7 +3815,7 @@
         // TODO: deprecate and remove mDefaultDns when we can do so safely.
         // For now, use it only when the network has Internet access. http://b/18327075
         final boolean useDefaultDns = networkAgent.networkCapabilities.hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET);
+                NET_CAPABILITY_INTERNET);
         final boolean flushDns = updateRoutes(newLp, oldLp, netId);
         updateDnses(newLp, oldLp, netId, flushDns, useDefaultDns);
 
@@ -3875,6 +3971,11 @@
             synchronized (networkAgent) {
                 networkAgent.networkCapabilities = networkCapabilities;
             }
+            if (networkAgent.lastValidated) {
+                networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
+                // There's no need to remove the capability if we think the network is unvalidated,
+                // because NetworkAgents don't set the validated capability.
+            }
             rematchAllNetworksAndRequests(networkAgent, networkAgent.getCurrentScore());
             notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
@@ -4142,7 +4243,7 @@
                 // the new one connected.
                 if (oldDefaultNetwork != null) {
                     mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
-                                              oldDefaultNetwork);
+                                              oldDefaultNetwork, true);
                 }
                 mDefaultInetConditionPublished = newNetwork.everValidated ? 100 : 0;
                 mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
@@ -4501,6 +4602,8 @@
     @Override
     public void factoryReset() {
         enforceConnectivityInternalPermission();
+        final int userId = UserHandle.getCallingUserId();
+
         // Turn airplane mode off
         setAirplaneMode(false);
 
@@ -4510,16 +4613,16 @@
         }
 
         // Turn VPN off
-        VpnConfig vpnConfig = getVpnConfig();
+        VpnConfig vpnConfig = getVpnConfig(userId);
         if (vpnConfig != null) {
             if (vpnConfig.legacy) {
-                prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+                prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
             } else {
-                // Prevent this app from initiating VPN connections in the future without
-                // user intervention.
-                setVpnPackageAuthorization(false);
+                // Prevent this app (packagename = vpnConfig.user) from initiating VPN connections
+                // in the future without user intervention.
+                setVpnPackageAuthorization(vpnConfig.user, userId, false);
 
-                prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN);
+                prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN, userId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 8a7c902..eac748f 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -50,12 +50,11 @@
     public final NetworkMisc networkMisc;
     // Indicates if netd has been told to create this Network.  Once created the appropriate routing
     // rules are setup and routes are added so packets can begin flowing over the Network.
-    // NOTE: This is a sticky bit; once set it is never cleared.
+    // This is a sticky bit; once set it is never cleared.
     public boolean created;
     // Set to true if this Network successfully passed validation or if it did not satisfy the
     // default NetworkRequest in which case validation will not be attempted.
-    // NOTE: This is a sticky bit; once set it is never cleared even if future validation attempts
-    // fail.
+    // This is a sticky bit; once set it is never cleared even if future validation attempts fail.
     public boolean everValidated;
 
     // The result of the last validation attempt on this network (true if validated, false if not).
@@ -65,6 +64,10 @@
     // TODO: Fix the network scoring code, remove this, and rename everValidated to validated.
     public boolean lastValidated;
 
+    // Whether a captive portal was ever detected on this network.
+    // This is a sticky bit; once set it is never cleared.
+    public boolean captivePortalDetected;
+
     // This represents the last score received from the NetworkAgent.
     private int currentScore;
     // Penalty applied to scores of Networks that have not been validated.
@@ -101,9 +104,6 @@
         currentScore = score;
         networkMonitor = new NetworkMonitor(context, handler, this, defaultRequest);
         networkMisc = misc;
-        created = false;
-        everValidated = false;
-        lastValidated = false;
     }
 
     public void addRequest(NetworkRequest networkRequest) {
@@ -166,6 +166,7 @@
                 "created{" + created + "}  " +
                 "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
                 "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
+                "captivePortalDetected{" + captivePortalDetected + "} " +
                 "}";
     }