Fixed VPN support for restricted profiles in split system user model

In a new split system user model, owner of a restricted profile is not limited
to just user0. restrictedProfileParentId field should be used to get an owner.

Bug: 22950929
Change-Id: I928319a9450e543972237a42267eb2404e117c83
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 7c09157..4c19ddd 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -49,6 +49,7 @@
     UserInfo getUserInfo(int userHandle);
     long getUserCreationTime(int userHandle);
     boolean isRestricted();
+    boolean canHaveRestrictedProfile(int userId);
     int getUserSerialNumber(int userHandle);
     int getUserHandle(int userSerialNumber);
     Bundle getUserRestrictions(int userHandle);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f58933a..37467ca 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -632,6 +632,19 @@
     }
 
     /**
+     * Checks if specified user can have restricted profile.
+     * @hide
+     */
+    public boolean canHaveRestrictedProfile(int userId) {
+        try {
+            return mService.canHaveRestrictedProfile(userId);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not check if user can have restricted profile", re);
+            return false;
+        }
+    }
+
+    /**
      * Checks if the calling app is running as a guest user.
      * @return whether the caller is a guest user.
      * @hide
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 0f9dd5c..e0823b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -182,10 +182,10 @@
     @Override
     public void onUserSwitched(int newUserId) {
         mCurrentUserId = newUserId;
-        if (mUserManager.getUserInfo(newUserId).isRestricted()) {
+        final UserInfo newUserInfo = mUserManager.getUserInfo(newUserId);
+        if (newUserInfo.isRestricted()) {
             // VPN for a restricted profile is routed through its owner user
-            // TODO: http://b/22950929
-            mVpnUserId = UserHandle.USER_SYSTEM;
+            mVpnUserId = newUserInfo.restrictedProfileParentId;
         } else {
             mVpnUserId = mCurrentUserId;
         }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 6190a5a..a7e6f11 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -754,6 +754,8 @@
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_USER_STARTING);
         intentFilter.addAction(Intent.ACTION_USER_STOPPING);
+        intentFilter.addAction(Intent.ACTION_USER_ADDED);
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
 
@@ -3525,6 +3527,26 @@
         }
     }
 
+    private void onUserAdded(int userId) {
+        synchronized(mVpns) {
+            final int vpnsSize = mVpns.size();
+            for (int i = 0; i < vpnsSize; i++) {
+                Vpn vpn = mVpns.valueAt(i);
+                vpn.onUserAdded(userId);
+            }
+        }
+    }
+
+    private void onUserRemoved(int userId) {
+        synchronized(mVpns) {
+            final int vpnsSize = mVpns.size();
+            for (int i = 0; i < vpnsSize; i++) {
+                Vpn vpn = mVpns.valueAt(i);
+                vpn.onUserRemoved(userId);
+            }
+        }
+    }
+
     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -3536,6 +3558,10 @@
                 onUserStart(userId);
             } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
                 onUserStop(userId);
+            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
+                onUserAdded(userId);
+            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                onUserRemoved(userId);
             }
         }
     };
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 3df3cd0..2bea278 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -20,9 +20,6 @@
 import static android.net.ConnectivityManager.NETID_UNSET;
 import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
-import static android.os.UserHandle.PER_USER_RANGE;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
 
 import android.Manifest;
 import android.app.AppGlobals;
@@ -34,13 +31,11 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
-import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -126,7 +121,6 @@
     /* list of users using this VPN. */
     @GuardedBy("this")
     private List<UidRange> mVpnUsers = null;
-    private BroadcastReceiver mUserIntentReceiver = null;
 
     // Handle of user initiating VPN.
     private final int mUserHandle;
@@ -146,31 +140,6 @@
         } catch (RemoteException e) {
             Log.wtf(TAG, "Problem registering observer", e);
         }
-        // TODO: http://b/22950929
-        if (userHandle == UserHandle.USER_SYSTEM) {
-            // Owner's VPN also needs to handle restricted users
-            mUserIntentReceiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    final String action = intent.getAction();
-                    final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
-                            UserHandle.USER_NULL);
-                    if (userHandle == UserHandle.USER_NULL) return;
-
-                    if (Intent.ACTION_USER_ADDED.equals(action)) {
-                        onUserAdded(userHandle);
-                    } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
-                        onUserRemoved(userHandle);
-                    }
-                }
-            };
-
-            IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(Intent.ACTION_USER_ADDED);
-            intentFilter.addAction(Intent.ACTION_USER_REMOVED);
-            mContext.registerReceiverAsUser(
-                    mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
-        }
 
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, "");
         // TODO: Copy metered attribute and bandwidths from physical transport, b/16207332
@@ -439,8 +408,8 @@
         }
 
         addVpnUserLocked(mUserHandle);
-        // If we are owner assign all Restricted Users to this VPN
-        if (mUserHandle == UserHandle.USER_OWNER) {
+        // If the user can have restricted profiles, assign all its restricted profiles to this VPN
+        if (canHaveRestrictedProfile(mUserHandle)) {
             token = Binder.clearCallingIdentity();
             List<UserInfo> users;
             try {
@@ -449,7 +418,7 @@
                 Binder.restoreCallingIdentity(token);
             }
             for (UserInfo user : users) {
-                if (user.isRestricted()) {
+                if (user.isRestricted() && (user.restrictedProfileParentId == mUserHandle)) {
                     addVpnUserLocked(user.id);
                 }
             }
@@ -457,6 +426,15 @@
         mNetworkAgent.addUidRanges(mVpnUsers.toArray(new UidRange[mVpnUsers.size()]));
     }
 
+    private boolean canHaveRestrictedProfile(int userId) {
+        long token = Binder.clearCallingIdentity();
+        try {
+            return UserManager.get(mContext).canHaveRestrictedProfile(userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     private void agentDisconnect(NetworkInfo networkInfo, NetworkAgent networkAgent) {
         networkInfo.setIsAvailable(false);
         networkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
@@ -681,12 +659,11 @@
         mStatusIntent = null;
     }
 
-    private void onUserAdded(int userHandle) {
-        // If the user is restricted tie them to the owner's VPN
-        synchronized(Vpn.this) {
-            UserManager mgr = UserManager.get(mContext);
-            UserInfo user = mgr.getUserInfo(userHandle);
-            if (user.isRestricted()) {
+    public void onUserAdded(int userHandle) {
+        // If the user is restricted tie them to the parent user's VPN
+        UserInfo user = UserManager.get(mContext).getUserInfo(userHandle);
+        if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) {
+            synchronized(Vpn.this) {
                 try {
                     addVpnUserLocked(userHandle);
                     if (mNetworkAgent != null) {
@@ -700,12 +677,11 @@
         }
     }
 
-    private void onUserRemoved(int userHandle) {
+    public void onUserRemoved(int userHandle) {
         // clean up if restricted
-        synchronized(Vpn.this) {
-            UserManager mgr = UserManager.get(mContext);
-            UserInfo user = mgr.getUserInfo(userHandle);
-            if (user.isRestricted()) {
+        UserInfo user = UserManager.get(mContext).getUserInfo(userHandle);
+        if (user.isRestricted() && user.restrictedProfileParentId == mUserHandle) {
+            synchronized(Vpn.this) {
                 try {
                     removeVpnUserLocked(userHandle);
                 } catch (Exception e) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0577d59..de106a1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -131,7 +131,7 @@
 
     private static final int MIN_USER_ID = 10;
 
-    private static final int USER_VERSION = 5;
+    private static final int USER_VERSION = 6;
 
     private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
 
@@ -408,6 +408,24 @@
         }
     }
 
+    @Override
+    public boolean canHaveRestrictedProfile(int userId) {
+        checkManageUsersPermission("canHaveRestrictedProfile");
+        synchronized (mPackagesLock) {
+            final UserInfo userInfo = getUserInfoLocked(userId);
+            if (userInfo == null || !userInfo.canHaveProfile()) {
+                return false;
+            }
+            if (!userInfo.isAdmin()) {
+                return false;
+            }
+        }
+        DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
+                Context.DEVICE_POLICY_SERVICE);
+        // restricted profile can be created if there is no DO set and the admin user has no PO
+        return dpm.getDeviceOwner() == null && dpm.getProfileOwnerAsUser(userId) == null;
+    }
+
     /*
      * Should be locked on mUsers before calling this.
      */
@@ -848,6 +866,20 @@
             userVersion = 5;
         }
 
+        if (userVersion < 6) {
+            final boolean splitSystemUser = UserManager.isSplitSystemUser();
+            for (int i = 0; i < mUsers.size(); i++) {
+                UserInfo user = mUsers.valueAt(i);
+                // In non-split mode, only user 0 can have restricted profiles
+                if (!splitSystemUser && user.isRestricted()
+                        && (user.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID)) {
+                    user.restrictedProfileParentId = UserHandle.USER_SYSTEM;
+                    scheduleWriteUserLocked(user);
+                }
+            }
+            userVersion = 6;
+        }
+
         if (userVersion < USER_VERSION) {
             Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to "
                     + USER_VERSION);