Merge "Starting point for User Restrictions API"
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 @@
             }
         }
     }
+
 }