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;