Starting point for User Restrictions API

Restrictions saved as key/value pairs, mostly booleans right now
but might be expanded to other types later.

Save and restore restrictions in the user manager service.
Enforce some of the restrictions at the framework level. Some
are enforced (also) at the app level, such as in Settings.

Change-Id: Id11ffe129cb6a177e094edf79635727388c26f40
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d80598c..c507245 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -666,6 +666,15 @@
     public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
 
     /**
+     * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+     * if the system failed to install the package because the user is restricted from installing
+     * apps.
+     * @hide
+     */
+    public static final int INSTALL_FAILED_USER_RESTRICTED = -111;
+
+    /**
      * Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
      * package's data directory.
      *
@@ -710,6 +719,15 @@
     public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
 
     /**
+     * Deletion failed return code: this is passed to the
+     * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
+     * failed to delete the package since the user is restricted.
+     *
+     * @hide
+     */
+    public static final int DELETE_FAILED_USER_RESTRICTED = -3;
+
+    /**
      * Return code that is passed to the {@link IPackageMoveObserver} by
      * {@link #movePackage(android.net.Uri, IPackageMoveObserver)} when the
      * package has been successfully moved by the system.
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index ec02ae0..34c9740 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -17,6 +17,7 @@
 
 package android.os;
 
+import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
@@ -37,4 +38,6 @@
     void wipeUser(int userHandle);
     int getUserSerialNumber(int userHandle);
     int getUserHandle(int userSerialNumber);
+    Bundle getUserRestrictions(int userHandle);
+    void setUserRestrictions(in Bundle restrictions, int userHandle);
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d73f99a..e4a5e7f 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -35,6 +35,42 @@
     private final IUserManager mService;
     private final Context mContext;
 
+    /**
+     * @hide
+     * Key for user restrictions. Specifies if a user is allowed to add or remove accounts.
+     * Type: Boolean
+     * @see #setUserRestrictions(Bundle)
+     * @see #getUserRestrictions()
+     */
+    public static final String ALLOW_MODIFY_ACCOUNTS = "modify_accounts";
+
+    /**
+     * @hide
+     * Key for user restrictions. Specifies if a user is allowed to change Wi-Fi access points.
+     * Type: Boolean
+     * @see #setUserRestrictions(Bundle)
+     * @see #getUserRestrictions()
+     */
+    public static final String ALLOW_CONFIG_WIFI = "config_wifi";
+
+    /**
+     * @hide
+     * Key for user restrictions. Specifies if a user is allowed to install applications.
+     * Type: Boolean
+     * @see #setUserRestrictions(Bundle)
+     * @see #getUserRestrictions()
+     */
+    public static final String ALLOW_INSTALL_APPS = "install_apps";
+
+    /**
+     * @hide
+     * Key for user restrictions. Specifies if a user is allowed to uninstall applications.
+     * Type: Boolean
+     * @see #setUserRestrictions(Bundle)
+     * @see #getUserRestrictions()
+     */
+    public static final String ALLOW_UNINSTALL_APPS = "uninstall_apps";
+
     /** @hide */
     public UserManager(Context context, IUserManager service) {
         mService = service;
@@ -132,6 +168,35 @@
         }
     }
 
+    /** @hide */
+    public Bundle getUserRestrictions() {
+        return getUserRestrictions(Process.myUserHandle());
+    }
+
+    /** @hide */
+    public Bundle getUserRestrictions(UserHandle userHandle) {
+        try {
+            return mService.getUserRestrictions(userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not get user restrictions", re);
+            return Bundle.EMPTY;
+        }
+    }
+
+    /** @hide */
+    public void setUserRestrictions(Bundle restrictions) {
+        setUserRestrictions(restrictions, Process.myUserHandle());
+    }
+
+    /** @hide */
+    public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) {
+        try {
+            mService.setUserRestrictions(restrictions, userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not set user restrictions", re);
+        }
+    }
+
     /**
      * Return the serial number for a user.  This is a device-unique
      * number assigned to that user; if the user is deleted and then a new
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 6a62809..18b4ec1 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -1889,7 +1889,7 @@
             mHandler.post(new Runnable() {
                 public void run() {
                     try {
-                        ActivityManagerNative.getDefault().switchUser(0);
+                        ActivityManagerNative.getDefault().switchUser(UserHandle.USER_OWNER);
                         ((UserManager) mContext.getSystemService(Context.USER_SERVICE))
                                 .removeUser(userHandle);
                     } catch (RemoteException re) {
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index 88603dc..2a62c17 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -21,6 +21,7 @@
 import android.accounts.AccountAndUser;
 import android.accounts.AccountAuthenticatorResponse;
 import android.accounts.AccountManager;
+import android.accounts.AccountManagerResponse;
 import android.accounts.AuthenticatorDescription;
 import android.accounts.GrantCredentialsPermissionActivity;
 import android.accounts.IAccountAuthenticator;
@@ -526,6 +527,9 @@
         }
         if (account == null) throw new IllegalArgumentException("account is null");
         checkAuthenticateAccountsPermission(account);
+        if (!canUserModifyAccounts(Binder.getCallingUid())) {
+            return false;
+        }
 
         UserAccounts accounts = getUserAccountsForCaller();
         // fails if the account already exists
@@ -679,6 +683,14 @@
         checkManageAccountsPermission();
         UserHandle user = Binder.getCallingUserHandle();
         UserAccounts accounts = getUserAccountsForCaller();
+        if (!canUserModifyAccounts(Binder.getCallingUid())) {
+            try {
+                response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
+                        "User cannot modify accounts");
+            } catch (RemoteException re) {
+            }
+        }
+
         long identityToken = clearCallingIdentity();
 
         cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
@@ -2312,6 +2324,17 @@
                 Manifest.permission.USE_CREDENTIALS);
     }
 
+    private boolean canUserModifyAccounts(int callingUid) {
+        if (callingUid != android.os.Process.myUid()) {
+            Bundle restrictions = getUserManager().getUserRestrictions(
+                    new UserHandle(UserHandle.getUserId(callingUid)));
+            if (!restrictions.getBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     public void updateAppPermission(Account account, String authTokenType, int uid, boolean value)
             throws RemoteException {
         final int callingUid = getCallingUid();
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 46d2cca..b480aa0 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -110,6 +110,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.Environment.UserEnvironment;
 import android.security.SystemKeyStore;
 import android.util.DisplayMetrics;
@@ -5649,6 +5650,14 @@
                 null);
 
         final int uid = Binder.getCallingUid();
+        if (!isUserAllowed(uid, UserManager.ALLOW_INSTALL_APPS)) {
+            try {
+                observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED);
+            } catch (RemoteException re) {
+            }
+            return;
+        }
+
         UserHandle user;
         if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) {
             user = UserHandle.ALL;
@@ -5685,6 +5694,9 @@
         PackageSetting pkgSetting;
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserId(uid);
+        if (!isUserAllowed(uid, UserManager.ALLOW_INSTALL_APPS)) {
+            return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
+        }
 
         long callingId = Binder.clearCallingIdentity();
         try {
@@ -5715,7 +5727,19 @@
 
         return PackageManager.INSTALL_SUCCEEDED;
     }
-    
+
+    private boolean isUserAllowed(int callingUid, String restrictionKey) {
+        if (callingUid != android.os.Process.myUid()) {
+            Bundle restrictions = sUserManager.getUserRestrictions(
+                    UserHandle.getUserId(callingUid));
+            if (!restrictions.getBoolean(UserManager.ALLOW_INSTALL_APPS)) {
+                Log.w(TAG, "User does not have permission to: " + restrictionKey);
+                return false;
+            }
+        }
+        return true;
+    }
+
     @Override
     public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
         mContext.enforceCallingOrSelfPermission(
@@ -8071,6 +8095,14 @@
                 android.Manifest.permission.DELETE_PACKAGES, null);
         // Queue up an async operation since the package deletion may take a little while.
         final int uid = Binder.getCallingUid();
+        if (!isUserAllowed(uid, UserManager.ALLOW_UNINSTALL_APPS)) {
+            try {
+                observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_USER_RESTRICTED);
+            } catch (RemoteException re) {
+            }
+            return;
+        }
+
         mHandler.post(new Runnable() {
             public void run() {
                 mHandler.removeCallbacks(this);
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index dbfe34d..5760dcd 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -30,6 +30,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -81,6 +82,7 @@
     private static final String ATTR_USER_VERSION = "version";
     private static final String TAG_USERS = "users";
     private static final String TAG_USER = "user";
+    private static final String TAG_RESTRICTIONS = "restrictions";
 
     private static final String USER_INFO_DIR = "system" + File.separator + "users";
     private static final String USER_LIST_FILENAME = "userlist.xml";
@@ -104,6 +106,7 @@
     private final File mBaseUserPath;
 
     private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
+    private final SparseArray<Bundle> mUserRestrictions = new SparseArray<Bundle>();
 
     /**
      * Set of user IDs being actively removed. Removed IDs linger in this set
@@ -343,6 +346,26 @@
         }
     }
 
+    @Override
+    public Bundle getUserRestrictions(int userId) {
+        // checkManageUsersPermission("getUserRestrictions");
+
+        synchronized (mPackagesLock) {
+            Bundle restrictions = mUserRestrictions.get(userId);
+            return restrictions != null ? restrictions : Bundle.EMPTY;
+        }
+    }
+
+    @Override
+    public void setUserRestrictions(Bundle restrictions, int userId) {
+        checkManageUsersPermission("setUserRestrictions");
+
+        synchronized (mPackagesLock) {
+            mUserRestrictions.get(userId).putAll(restrictions);
+            writeUserLocked(mUsers.get(userId));
+        }
+    }
+
     /**
      * Check if we've hit the limit of how many users can be created.
      */
@@ -454,7 +477,7 @@
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                 if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
                     String id = parser.getAttributeValue(null, ATTR_ID);
-                    UserInfo user = readUser(Integer.parseInt(id));
+                    UserInfo user = readUserLocked(Integer.parseInt(id));
 
                     if (user != null) {
                         mUsers.put(user.id, user);
@@ -568,6 +591,15 @@
             serializer.text(userInfo.name);
             serializer.endTag(null, TAG_NAME);
 
+            Bundle restrictions = mUserRestrictions.get(userInfo.id);
+            if (restrictions != null) {
+                serializer.startTag(null, TAG_RESTRICTIONS);
+                writeBoolean(serializer, restrictions, UserManager.ALLOW_CONFIG_WIFI);
+                writeBoolean(serializer, restrictions, UserManager.ALLOW_MODIFY_ACCOUNTS);
+                writeBoolean(serializer, restrictions, UserManager.ALLOW_INSTALL_APPS);
+                writeBoolean(serializer, restrictions, UserManager.ALLOW_UNINSTALL_APPS);
+                serializer.endTag(null, TAG_RESTRICTIONS);
+            }
             serializer.endTag(null, TAG_USER);
 
             serializer.endDocument();
@@ -620,7 +652,7 @@
         }
     }
 
-    private UserInfo readUser(int id) {
+    private UserInfo readUserLocked(int id) {
         int flags = 0;
         int serialNumber = id;
         String name = null;
@@ -628,6 +660,8 @@
         long creationTime = 0L;
         long lastLoggedInTime = 0L;
         boolean partial = false;
+        Bundle restrictions = new Bundle();
+        initRestrictionsToDefaults(restrictions);
 
         FileInputStream fis = null;
         try {
@@ -663,13 +697,23 @@
                     partial = true;
                 }
 
-                while ((type = parser.next()) != XmlPullParser.START_TAG
-                        && type != XmlPullParser.END_DOCUMENT) {
-                }
-                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
-                    type = parser.next();
-                    if (type == XmlPullParser.TEXT) {
-                        name = parser.getText();
+                int outerDepth = parser.getDepth();
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                       && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                        continue;
+                    }
+                    String tag = parser.getName();
+                    if (TAG_NAME.equals(tag)) {
+                        type = parser.next();
+                        if (type == XmlPullParser.TEXT) {
+                            name = parser.getText();
+                        }
+                    } else if (TAG_RESTRICTIONS.equals(tag)) {
+                        readBoolean(parser, restrictions, UserManager.ALLOW_CONFIG_WIFI);
+                        readBoolean(parser, restrictions, UserManager.ALLOW_MODIFY_ACCOUNTS);
+                        readBoolean(parser, restrictions, UserManager.ALLOW_INSTALL_APPS);
+                        readBoolean(parser, restrictions, UserManager.ALLOW_UNINSTALL_APPS);
                     }
                 }
             }
@@ -679,6 +723,7 @@
             userInfo.creationTime = creationTime;
             userInfo.lastLoggedInTime = lastLoggedInTime;
             userInfo.partial = partial;
+            mUserRestrictions.append(id, restrictions);
             return userInfo;
 
         } catch (IOException ioe) {
@@ -694,6 +739,27 @@
         return null;
     }
 
+    private void readBoolean(XmlPullParser parser, Bundle restrictions,
+            String restrictionKey) {
+        String value = parser.getAttributeValue(null, restrictionKey);
+        restrictions.putBoolean(restrictionKey, value == null ? true : Boolean.parseBoolean(value));
+    }
+
+    private void writeBoolean(XmlSerializer xml, Bundle restrictions, String restrictionKey)
+            throws IOException {
+        if (restrictions.containsKey(restrictionKey)) {
+            xml.attribute(null, restrictionKey,
+                    Boolean.toString(restrictions.getBoolean(restrictionKey)));
+        }
+    }
+
+    private void initRestrictionsToDefaults(Bundle restrictions) {
+        restrictions.putBoolean(UserManager.ALLOW_CONFIG_WIFI, true);
+        restrictions.putBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS, true);
+        restrictions.putBoolean(UserManager.ALLOW_INSTALL_APPS, true);
+        restrictions.putBoolean(UserManager.ALLOW_UNINSTALL_APPS, true);
+    }
+
     private int readIntAttribute(XmlPullParser parser, String attr, int defaultValue) {
         String valueString = parser.getAttributeValue(null, attr);
         if (valueString == null) return defaultValue;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 1758d93..01b65a3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
+import android.os.Bundle;
 import android.os.Debug;
 import android.os.Environment;
 import android.os.UserManager;
@@ -140,6 +141,20 @@
         }
     }
 
+    public void testRestrictions() {
+        List<UserInfo> users = mUserManager.getUsers();
+        if (users.size() > 1) {
+            Bundle restrictions = new Bundle();
+            restrictions.putBoolean(UserManager.ALLOW_INSTALL_APPS, false);
+            restrictions.putBoolean(UserManager.ALLOW_CONFIG_WIFI, true);
+            mUserManager.setUserRestrictions(restrictions, users.get(1).id);
+            Bundle stored = mUserManager.getUserRestrictions(users.get(1).id);
+            assertEquals(stored.getBoolean(UserManager.ALLOW_CONFIG_WIFI), true);
+            assertEquals(stored.getBoolean(UserManager.ALLOW_UNINSTALL_APPS), true);
+            assertEquals(stored.getBoolean(UserManager.ALLOW_INSTALL_APPS), false);
+        }
+    }
+
     private void removeUser(int userId) {
         synchronized (mUserLock) {
             mUserManager.removeUser(userId);
@@ -151,4 +166,5 @@
             }
         }
     }
+
 }