Block setting wallpapers from managed profiles.

Silently fail when a managed profile app tries to change the
wallpaper and return default values for getters in that case.
This is implemented through a new AppOp that is controlled by
a new user restriction that will be set during provisioning.

Bug: 18725052
Change-Id: I1601852617e738be86560f054daf3435dd9f5a9f
diff --git a/api/current.txt b/api/current.txt
index e6d8546..99d8fd4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5369,6 +5369,7 @@
     method public static android.app.WallpaperManager getInstance(android.content.Context);
     method public android.app.WallpaperInfo getWallpaperInfo();
     method public boolean hasResourceWallpaper(int);
+    method public boolean isWallpaperSupported();
     method public android.graphics.drawable.Drawable peekDrawable();
     method public android.graphics.drawable.Drawable peekFastDrawable();
     method public void sendWallpaperCommand(android.os.IBinder, java.lang.String, int, int, int, android.os.Bundle);
diff --git a/api/system-current.txt b/api/system-current.txt
index ca84133..21aee04 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5460,6 +5460,7 @@
     method public static android.app.WallpaperManager getInstance(android.content.Context);
     method public android.app.WallpaperInfo getWallpaperInfo();
     method public boolean hasResourceWallpaper(int);
+    method public boolean isWallpaperSupported();
     method public android.graphics.drawable.Drawable peekDrawable();
     method public android.graphics.drawable.Drawable peekFastDrawable();
     method public void sendWallpaperCommand(android.os.IBinder, java.lang.String, int, int, int, android.os.Bundle);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 95870cf..4bd2332 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -206,8 +206,10 @@
     public static final int OP_PROJECT_MEDIA = 46;
     /** @hide Activate a VPN connection without user intervention. */
     public static final int OP_ACTIVATE_VPN = 47;
+    /** @hide Access the WallpaperManagerAPI to write wallpapers. */
+    public static final int OP_WRITE_WALLPAPER = 48;
     /** @hide */
-    public static final int _NUM_OP = 48;
+    public static final int _NUM_OP = 49;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION =
@@ -285,6 +287,7 @@
             OP_TOAST_WINDOW,
             OP_PROJECT_MEDIA,
             OP_ACTIVATE_VPN,
+            OP_WRITE_WALLPAPER,
     };
 
     /**
@@ -340,6 +343,7 @@
             null,
             null,
             OPSTR_ACTIVATE_VPN,
+            null,
     };
 
     /**
@@ -395,6 +399,7 @@
             "TOAST_WINDOW",
             "PROJECT_MEDIA",
             "ACTIVATE_VPN",
+            "WRITE_WALLPAPER",
     };
 
     /**
@@ -450,6 +455,7 @@
             null, // no permission for displaying toasts
             null, // no permission for projecting media
             null, // no permission for activating vpn
+            null, // no permission for supporting wallpaper
     };
 
     /**
@@ -506,6 +512,7 @@
             UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW
             null, //PROJECT_MEDIA
             UserManager.DISALLOW_CONFIG_VPN, // ACTIVATE_VPN
+            UserManager.DISALLOW_WALLPAPER, // WRITE_WALLPAPER
     };
 
     /**
@@ -561,6 +568,7 @@
             true, //TOAST_WINDOW
             false, //PROJECT_MEDIA
             false, //ACTIVATE_VPN
+            false, //WALLPAPER
     };
 
     /**
@@ -615,6 +623,7 @@
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_IGNORED, // OP_PROJECT_MEDIA
             AppOpsManager.MODE_IGNORED, // OP_ACTIVATE_VPN
+            AppOpsManager.MODE_ALLOWED,
     };
 
     /**
@@ -673,6 +682,7 @@
             false,
             false,
             false,
+            false,
     };
 
     private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>();
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 3b5900b..ccba250 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -29,13 +29,18 @@
     /**
      * Set the wallpaper.
      */
-    ParcelFileDescriptor setWallpaper(String name);
+    ParcelFileDescriptor setWallpaper(String name, in String callingPackage);
     
     /**
      * Set the live wallpaper.
      */
+    void setWallpaperComponentChecked(in ComponentName name, in String callingPackage);
+
+    /**
+     * Set the live wallpaper.
+     */
     void setWallpaperComponent(in ComponentName name);
-    
+
     /**
      * Get the wallpaper.
      */
@@ -50,7 +55,7 @@
     /**
      * Clear the wallpaper.
      */
-    void clearWallpaper();
+    void clearWallpaper(in String callingPackage);
 
     /**
      * Return whether there is a wallpaper set with the given name.
@@ -61,7 +66,7 @@
      * Sets the dimension hint for the wallpaper. These hints indicate the desired
      * minimum width and height for the wallpaper.
      */
-    void setDimensionHints(in int width, in int height);
+    void setDimensionHints(in int width, in int height, in String callingPackage);
 
     /**
      * Returns the desired minimum width for the wallpaper.
@@ -76,7 +81,7 @@
     /**
      * Sets extra padding that we would like the wallpaper to have outside of the display.
      */
-    void setDisplayPadding(in Rect padding);
+    void setDisplayPadding(in Rect padding, in String callingPackage);
 
     /**
      * Returns the name of the wallpaper. Private API.
@@ -87,4 +92,9 @@
      * Informs the service that wallpaper settings have been restored. Private API.
      */
     void settingsRestored();
+
+    /**
+     * Check whether wallpapers are supported for the calling user.
+     */
+    boolean isWallpaperSupported(in String callingPackage);
 }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index badb606..22e79b6 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -64,7 +64,10 @@
  * Provides access to the system wallpaper. With WallpaperManager, you can
  * get the current wallpaper, get the desired dimensions for the wallpaper, set
  * the wallpaper, and more. Get an instance of WallpaperManager with
- * {@link #getInstance(android.content.Context) getInstance()}. 
+ * {@link #getInstance(android.content.Context) getInstance()}.
+ *
+ * <p> An app can check whether wallpapers are supported for the current user, by calling
+ * {@link #isWallpaperSupported()}.
  */
 public class WallpaperManager {
     private static String TAG = "WallpaperManager";
@@ -249,6 +252,15 @@
 
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
             synchronized (this) {
+                if (mService != null) {
+                    try {
+                        if (!mService.isWallpaperSupported(context.getOpPackageName())) {
+                            return null;
+                        }
+                    } catch (RemoteException e) {
+                        // Ignore
+                    }
+                }
                 if (mWallpaper != null) {
                     return mWallpaper;
                 }
@@ -618,7 +630,9 @@
      * wallpaper will require reloading it again from disk.
      */
     public void forgetLoadedWallpaper() {
-        sGlobals.forgetLoadedWallpaper();
+        if (isWallpaperSupported()) {
+            sGlobals.forgetLoadedWallpaper();
+        }
     }
 
     /**
@@ -717,7 +731,7 @@
             Resources resources = mContext.getResources();
             /* Set the wallpaper to the default values */
             ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
-                    "res:" + resources.getResourceName(resid));
+                    "res:" + resources.getResourceName(resid), mContext.getOpPackageName());
             if (fd != null) {
                 FileOutputStream fos = null;
                 try {
@@ -753,7 +767,8 @@
             return;
         }
         try {
-            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
+            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
+                    mContext.getOpPackageName());
             if (fd == null) {
                 return;
             }
@@ -792,7 +807,8 @@
             return;
         }
         try {
-            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
+            ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
+                    mContext.getOpPackageName());
             if (fd == null) {
                 return;
             }
@@ -945,7 +961,8 @@
             if (sGlobals.mService == null) {
                 Log.w(TAG, "WallpaperService not running");
             } else {
-                sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);
+                sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight,
+                        mContext.getOpPackageName());
             }
         } catch (RemoteException e) {
             // Ignore
@@ -966,7 +983,7 @@
             if (sGlobals.mService == null) {
                 Log.w(TAG, "WallpaperService not running");
             } else {
-                sGlobals.mService.setDisplayPadding(padding);
+                sGlobals.mService.setDisplayPadding(padding, mContext.getOpPackageName());
             }
         } catch (RemoteException e) {
             // Ignore
@@ -1006,7 +1023,7 @@
             return;
         }
         try {
-            sGlobals.mService.clearWallpaper();
+            sGlobals.mService.clearWallpaper(mContext.getOpPackageName());
         } catch (RemoteException e) {
             // Ignore
         }
@@ -1027,7 +1044,7 @@
             return false;
         }
         try {
-            sGlobals.mService.setWallpaperComponent(name);
+            sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName());
             return true;
         } catch (RemoteException e) {
             // Ignore
@@ -1096,7 +1113,24 @@
             // Ignore.
         }
     }
-    
+
+    /**
+     * Returns whether wallpapers are supported for the calling user. If this function returns
+     * false, any attempts to changing the wallpaper will have no effect.
+     */
+    public boolean isWallpaperSupported() {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+        } else {
+            try {
+                return sGlobals.mService.isWallpaperSupported(mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        }
+        return false;
+    }
+
     /**
      * Clear the offsets previously associated with this window through
      * {@link #setWallpaperOffsets(IBinder, float, float)}.  This reverts
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 650f3b3..706e0d0 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -391,6 +391,15 @@
     public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
 
     /**
+     * Hidden user restriction to disallow access to wallpaper manager APIs. This user restriction
+     * is always set for managed profiles.
+     * @hide
+     * @see #setUserRestrictions(Bundle)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_WALLPAPER = "no_wallpaper";
+
+    /**
      * Application restriction key that is used to indicate the pending arrival
      * of real restrictions for the app.
      *
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 2729392..e4f5e7d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -917,6 +917,7 @@
         writeBoolean(serializer, restrictions, UserManager.DISALLOW_CREATE_WINDOWS);
         writeBoolean(serializer, restrictions, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
         writeBoolean(serializer, restrictions, UserManager.DISALLOW_OUTGOING_BEAM);
+        writeBoolean(serializer, restrictions, UserManager.DISALLOW_WALLPAPER);
         serializer.endTag(null, TAG_RESTRICTIONS);
     }
 
@@ -1063,6 +1064,7 @@
         readBoolean(parser, restrictions, UserManager.DISALLOW_CREATE_WINDOWS);
         readBoolean(parser, restrictions, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
         readBoolean(parser, restrictions, UserManager.DISALLOW_OUTGOING_BEAM);
+        readBoolean(parser, restrictions, UserManager.DISALLOW_WALLPAPER);
     }
 
     private void readBoolean(XmlPullParser parser, Bundle restrictions,
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b48fadb2..99cf8df 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -20,6 +20,7 @@
 
 import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.IUserSwitchObserver;
 import android.app.IWallpaperManager;
 import android.app.IWallpaperManagerCallback;
@@ -164,6 +165,7 @@
     final IWindowManager mIWindowManager;
     final IPackageManager mIPackageManager;
     final MyPackageMonitor mMonitor;
+    final AppOpsManager mAppOpsManager;
     WallpaperData mLastWallpaper;
 
     /**
@@ -478,6 +480,7 @@
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         mIPackageManager = AppGlobals.getPackageManager();
+        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mMonitor = new MyPackageMonitor();
         mMonitor.register(context, null, UserHandle.ALL, true);
         getWallpaperDir(UserHandle.USER_OWNER).mkdirs();
@@ -613,8 +616,12 @@
         }
     }
 
-    public void clearWallpaper() {
+    public void clearWallpaper(String callingPackage) {
         if (DEBUG) Slog.v(TAG, "clearWallpaper");
+        checkPermission(android.Manifest.permission.SET_WALLPAPER);
+        if (!isWallpaperSupported(callingPackage)) {
+            return;
+        }
         synchronized (mLock) {
             clearWallpaperLocked(false, UserHandle.getCallingUserId(), null);
         }
@@ -622,6 +629,9 @@
 
     void clearWallpaperLocked(boolean defaultFailed, int userId, IRemoteCallback reply) {
         WallpaperData wallpaper = mWallpaperMap.get(userId);
+        if (wallpaper == null) {
+            return;
+        }
         File f = new File(getWallpaperDir(userId), WALLPAPER);
         if (f.exists()) {
             f.delete();
@@ -668,6 +678,10 @@
                 Binder.restoreCallingIdentity(ident);
             }
             for (UserInfo user: users) {
+                // ignore managed profiles
+                if (user.isManagedProfile()) {
+                    continue;
+                }
                 WallpaperData wd = mWallpaperMap.get(user.id);
                 if (wd == null) {
                     // User hasn't started yet, so load her settings to peek at the wallpaper
@@ -690,8 +704,12 @@
         return p;
     }
 
-    public void setDimensionHints(int width, int height) throws RemoteException {
+    public void setDimensionHints(int width, int height, String callingPackage)
+            throws RemoteException {
         checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
+        if (!isWallpaperSupported(callingPackage)) {
+            return;
+        }
         synchronized (mLock) {
             int userId = UserHandle.getCallingUserId();
             WallpaperData wallpaper = mWallpaperMap.get(userId);
@@ -733,19 +751,30 @@
     public int getWidthHint() throws RemoteException {
         synchronized (mLock) {
             WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
-            return wallpaper.width;
+            if (wallpaper != null) {
+                return wallpaper.width;
+            } else {
+                return 0;
+            }
         }
     }
 
     public int getHeightHint() throws RemoteException {
         synchronized (mLock) {
             WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
-            return wallpaper.height;
+            if (wallpaper != null) {
+                return wallpaper.height;
+            } else {
+                return 0;
+            }
         }
     }
 
-    public void setDisplayPadding(Rect padding) {
+    public void setDisplayPadding(Rect padding, String callingPackage) {
         checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
+        if (!isWallpaperSupported(callingPackage)) {
+            return;
+        }
         synchronized (mLock) {
             int userId = UserHandle.getCallingUserId();
             WallpaperData wallpaper = mWallpaperMap.get(userId);
@@ -791,6 +820,9 @@
                 wallpaperUserId = UserHandle.getUserId(callingUid);
             }
             WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId);
+            if (wallpaper == null) {
+                return null;
+            }
             try {
                 if (outParams != null) {
                     outParams.putInt("width", wallpaper.width);
@@ -814,15 +846,18 @@
         int userId = UserHandle.getCallingUserId();
         synchronized (mLock) {
             WallpaperData wallpaper = mWallpaperMap.get(userId);
-            if (wallpaper.connection != null) {
+            if (wallpaper != null && wallpaper.connection != null) {
                 return wallpaper.connection.mInfo;
             }
             return null;
         }
     }
 
-    public ParcelFileDescriptor setWallpaper(String name) {
+    public ParcelFileDescriptor setWallpaper(String name, String callingPackage) {
         checkPermission(android.Manifest.permission.SET_WALLPAPER);
+        if (!isWallpaperSupported(callingPackage)) {
+            return null;
+        }
         synchronized (mLock) {
             if (DEBUG) Slog.v(TAG, "setWallpaper");
             int userId = UserHandle.getCallingUserId();
@@ -868,6 +903,13 @@
         return null;
     }
 
+    public void setWallpaperComponentChecked(ComponentName name, String callingPackage) {
+        if (isWallpaperSupported(callingPackage)) {
+            setWallpaperComponent(name);
+        }
+    }
+
+    // ToDo: Remove this version of the function
     public void setWallpaperComponent(ComponentName name) {
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
         synchronized (mLock) {
@@ -1097,6 +1139,15 @@
         }
     }
 
+    /**
+     * Certain user types do not support wallpapers (e.g. managed profiles). The check is
+     * implemented through through the OP_WRITE_WALLPAPER AppOp.
+     */
+    public boolean isWallpaperSupported(String callingPackage) {
+        return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_WRITE_WALLPAPER, Binder.getCallingUid(),
+                callingPackage) == AppOpsManager.MODE_ALLOWED;
+    }
+
     private static JournaledFile makeJournaledFile(int userId) {
         final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
         return new JournaledFile(new File(base), new File(base + ".tmp"));
@@ -1174,7 +1225,7 @@
 
     private void loadSettingsLocked(int userId) {
         if (DEBUG) Slog.v(TAG, "loadSettingsLocked");
-        
+
         JournaledFile journal = makeJournaledFile(userId);
         FileInputStream stream = null;
         File file = journal.chooseForRead();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b90666f..00d7971 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -180,6 +180,14 @@
         DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SMS);
     }
 
+    // The following user restrictions cannot be changed by any active admin, including device
+    // owner and profile owner.
+    private static final Set<String> IMMUTABLE_USER_RESTRICTIONS;
+    static {
+        IMMUTABLE_USER_RESTRICTIONS = new HashSet();
+        IMMUTABLE_USER_RESTRICTIONS.add(UserManager.DISALLOW_WALLPAPER);
+    }
+
     private static final Set<String> SECURE_SETTINGS_WHITELIST;
     private static final Set<String> SECURE_SETTINGS_DEVICEOWNER_WHITELIST;
     private static final Set<String> GLOBAL_SETTINGS_WHITELIST;
@@ -4953,6 +4961,9 @@
                     && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
                 throw new SecurityException("Profile owners cannot set user restriction " + key);
             }
+            if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
+                throw new SecurityException("User restriction " + key + " cannot be changed");
+            }
             boolean alreadyRestricted = mUserManager.hasUserRestriction(key, user);
 
             IAudioService iAudioService = null;