From 1c7c319bb89b9988bfd12afc3e8d89449fd163fc Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Thu, 26 Jun 2014 12:52:18 -0400 Subject: [PATCH] User restriction for disallowing window creation Block any types of windows that could by used by apps to create views on top of a locked app. This can be used by device admins in conjunction with lock task mode. Added a way for system (and priv apps) to bypass user restrictions for specified op codes. Bug: 15279535 Change-Id: I2381530ef6226a5bb32a99bb4030baafb39bf564 --- api/current.txt | 1 + core/java/android/app/AppOpsManager.java | 81 +++++++++++++++++++++- .../android/app/admin/DevicePolicyManager.java | 2 + core/java/android/os/UserManager.java | 19 +++++ .../internal/policy/impl/PhoneWindowManager.java | 1 + .../java/com/android/server/AppOpsService.java | 76 ++++++++++++++++---- .../com/android/server/pm/UserManagerService.java | 2 + 7 files changed, 167 insertions(+), 15 deletions(-) diff --git a/api/current.txt b/api/current.txt index 97269373ef35..e7f950fec449 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21704,6 +21704,7 @@ package android.os { field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering"; field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn"; field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi"; + field public static final java.lang.String DISALLOW_CREATE_WINDOWS = "no_create_windows"; field public static final java.lang.String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features"; field public static final java.lang.String DISALLOW_FACTORY_RESET = "no_factory_reset"; field public static final java.lang.String DISALLOW_INSTALL_APPS = "no_install_apps"; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index a480219fbf40..b7e64a225da7 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -191,7 +191,9 @@ public class AppOpsManager { /** @hide */ public static final int OP_MUTE_MICROPHONE = 44; /** @hide */ - public static final int _NUM_OP = 45; + public static final int OP_TOAST_WINDOW = 45; + /** @hide */ + public static final int _NUM_OP = 46; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = @@ -259,7 +261,8 @@ public class AppOpsManager { OP_COARSE_LOCATION, OP_COARSE_LOCATION, OP_GET_USAGE_STATS, - OP_MUTE_MICROPHONE + OP_MUTE_MICROPHONE, + OP_TOAST_WINDOW, }; /** @@ -312,6 +315,7 @@ public class AppOpsManager { OPSTR_MONITOR_HIGH_POWER_LOCATION, null, null, + null, }; /** @@ -364,6 +368,7 @@ public class AppOpsManager { "MONITOR_HIGH_POWER_LOCATION", "GET_USAGE_STATS", "OP_MUTE_MICROPHONE", + "TOAST_WINDOW", }; /** @@ -416,6 +421,7 @@ public class AppOpsManager { null, // no permission for high power location monitoring android.Manifest.permission.PACKAGE_USAGE_STATS, null, // no permission for muting/unmuting microphone + null, // no permission for displaying toasts }; /** @@ -448,7 +454,7 @@ public class AppOpsManager { null, //READ_ICC_SMS null, //WRITE_ICC_SMS null, //WRITE_SETTINGS - null, //SYSTEM_ALERT_WINDOW + UserManager.DISALLOW_CREATE_WINDOWS, //SYSTEM_ALERT_WINDOW null, //ACCESS_NOTIFICATIONS null, //CAMERA null, //RECORD_AUDIO @@ -469,6 +475,60 @@ public class AppOpsManager { null, //MONITOR_HIGH_POWER_LOCATION null, //GET_USAGE_STATS UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE + UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW + }; + + /** + * This specifies whether each option should allow the system + * (and system ui) to bypass the user restriction when active. + */ + private static boolean[] sOpAllowSystemRestrictionBypass = new boolean[] { + false, //COARSE_LOCATION + false, //FINE_LOCATION + false, //GPS + false, //VIBRATE + false, //READ_CONTACTS + false, //WRITE_CONTACTS + false, //READ_CALL_LOG + false, //WRITE_CALL_LOG + false, //READ_CALENDAR + false, //WRITE_CALENDAR + false, //WIFI_SCAN + false, //POST_NOTIFICATION + false, //NEIGHBORING_CELLS + false, //CALL_PHONE + false, //READ_SMS + false, //WRITE_SMS + false, //RECEIVE_SMS + false, //RECEIVE_EMERGECY_SMS + false, //RECEIVE_MMS + false, //RECEIVE_WAP_PUSH + false, //SEND_SMS + false, //READ_ICC_SMS + false, //WRITE_ICC_SMS + false, //WRITE_SETTINGS + true, //SYSTEM_ALERT_WINDOW + false, //ACCESS_NOTIFICATIONS + false, //CAMERA + false, //RECORD_AUDIO + false, //PLAY_AUDIO + false, //READ_CLIPBOARD + false, //WRITE_CLIPBOARD + false, //TAKE_MEDIA_BUTTONS + false, //TAKE_AUDIO_FOCUS + false, //AUDIO_MASTER_VOLUME + false, //AUDIO_VOICE_VOLUME + false, //AUDIO_RING_VOLUME + false, //AUDIO_MEDIA_VOLUME + false, //AUDIO_ALARM_VOLUME + false, //AUDIO_NOTIFICATION_VOLUME + false, //AUDIO_BLUETOOTH_VOLUME + false, //WAKE_LOCK + false, //MONITOR_LOCATION + false, //MONITOR_HIGH_POWER_LOCATION + false, //GET_USAGE_STATS + false, // MUTE_MICROPHONE + true, // TOAST_WINDOW }; /** @@ -520,6 +580,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_IGNORED, // OP_GET_USAGE_STATS AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, }; /** @@ -575,6 +636,7 @@ public class AppOpsManager { false, false, false, + false, }; private static HashMap sOpStrToOp = new HashMap(); @@ -608,6 +670,10 @@ public class AppOpsManager { throw new IllegalStateException("sOpRestrictions length " + sOpRestrictions.length + " should be " + _NUM_OP); } + if (sOpAllowSystemRestrictionBypass.length != _NUM_OP) { + throw new IllegalStateException("sOpAllowSYstemRestrictionsBypass length " + + sOpRestrictions.length + " should be " + _NUM_OP); + } for (int i=0; i<_NUM_OP; i++) { if (sOpToString[i] != null) { sOpStrToOp.put(sOpToString[i], i); @@ -649,6 +715,15 @@ public class AppOpsManager { } /** + * Retrieve whether the op allows the system (and system ui) to + * bypass the user restriction. + * @hide + */ + public static boolean opAllowSystemBypassRestriction(int op) { + return sOpAllowSystemRestrictionBypass[op]; + } + + /** * Retrieve the default mode for the operation. * @hide */ diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index d18647a3622a..df51ff5fed7b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2586,6 +2586,8 @@ public class DevicePolicyManager { * @param packages The list of packages allowed to enter lock task mode * * @see Activity#startLockTask() + * @see DeviceAdminReceiver#onLockTaskModeChanged(Context, Intent, boolean, String) + * @see UserManager#DISALLOW_CREATE_WINDOWS */ public void setLockTaskPackages(String[] packages) throws SecurityException { if (mService != null) { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index b980e81e7729..a54320fae0a7 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -27,6 +27,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.provider.Settings; import android.util.Log; +import android.view.WindowManager.LayoutParams; import com.android.internal.R; @@ -278,6 +279,24 @@ public class UserManager { */ public static final String DISALLOW_TELEPHONY = "no_telephony"; + /** + * Key for user restrictions. Specifies that windows besides app windows should not be + * created. This will block the creation of the following types of windows. + *
  • {@link LayoutParams#TYPE_TOAST}
  • + *
  • {@link LayoutParams#TYPE_PHONE}
  • + *
  • {@link LayoutParams#TYPE_PRIORITY_PHONE}
  • + *
  • {@link LayoutParams#TYPE_SYSTEM_ALERT}
  • + *
  • {@link LayoutParams#TYPE_SYSTEM_ERROR}
  • + *
  • {@link LayoutParams#TYPE_SYSTEM_OVERLAY}
  • + * + *

    The default value is false. + *

    + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows"; + /** @hide */ public static final int PIN_VERIFICATION_FAILED_INCORRECT = -3; /** @hide */ diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index ac53d0daedb3..4458a8d64da3 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -1365,6 +1365,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // XXX right now the app process has complete control over // this... should introduce a token to let the system // monitor/control what they are doing. + outAppOp[0] = AppOpsManager.OP_TOAST_WINDOW; break; case TYPE_DREAM: case TYPE_INPUT_METHOD: diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index 176ba5db3b67..14a462e7442d 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -29,8 +29,11 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import android.app.ActivityThread; import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.media.AudioService; @@ -102,10 +105,12 @@ public class AppOpsService extends IAppOpsService.Stub { public final static class Ops extends SparseArray { public final String packageName; public final int uid; + public final boolean isPrivileged; - public Ops(String _packageName, int _uid) { + public Ops(String _packageName, int _uid, boolean _isPrivileged) { packageName = _packageName; uid = _uid; + isPrivileged = _isPrivileged; } } @@ -560,7 +565,7 @@ public class AppOpsService extends IAppOpsService.Stub { verifyIncomingUid(uid); verifyIncomingOp(code); synchronized (this) { - if (isOpRestricted(uid, code)) { + if (isOpRestricted(uid, code, packageName)) { return AppOpsManager.MODE_IGNORED; } Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false); @@ -646,7 +651,7 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_ERRORED; } Op op = getOpLocked(ops, code, true); - if (isOpRestricted(uid, code)) { + if (isOpRestricted(uid, code, packageName)) { return AppOpsManager.MODE_IGNORED; } if (op.duration == -1) { @@ -683,7 +688,7 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_ERRORED; } Op op = getOpLocked(ops, code, true); - if (isOpRestricted(uid, code)) { + if (isOpRestricted(uid, code, packageName)) { return AppOpsManager.MODE_IGNORED; } final int switchCode = AppOpsManager.opToSwitch(code); @@ -782,6 +787,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (!edit) { return null; } + boolean isPrivileged = false; // This is the first time we have seen this package name under this uid, // so let's make sure it is valid. if (uid != 0) { @@ -789,12 +795,19 @@ public class AppOpsService extends IAppOpsService.Stub { try { int pkgUid = -1; try { - pkgUid = mContext.getPackageManager().getPackageUid(packageName, - UserHandle.getUserId(uid)); - } catch (NameNotFoundException e) { - if ("media".equals(packageName)) { - pkgUid = Process.MEDIA_UID; + ApplicationInfo appInfo = ActivityThread.getPackageManager() + .getApplicationInfo(packageName, 0, UserHandle.getUserId(uid)); + if (appInfo != null) { + pkgUid = appInfo.uid; + isPrivileged = (appInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0; + } else { + if ("media".equals(packageName)) { + pkgUid = Process.MEDIA_UID; + isPrivileged = false; + } } + } catch (RemoteException e) { + Slog.w(TAG, "Could not contact PackageManager", e); } if (pkgUid != uid) { // Oops! The package name is not valid for the uid they are calling @@ -807,7 +820,7 @@ public class AppOpsService extends IAppOpsService.Stub { Binder.restoreCallingIdentity(ident); } } - ops = new Ops(packageName, uid); + ops = new Ops(packageName, uid, isPrivileged); pkgOps.put(packageName, ops); } return ops; @@ -851,10 +864,18 @@ public class AppOpsService extends IAppOpsService.Stub { return op; } - private boolean isOpRestricted(int uid, int code) { + private boolean isOpRestricted(int uid, int code, String packageName) { int userHandle = UserHandle.getUserId(uid); boolean[] opRestrictions = mOpRestrictions.get(userHandle); if ((opRestrictions != null) && opRestrictions[code]) { + if (AppOpsManager.opAllowSystemBypassRestriction(code)) { + synchronized (this) { + Ops ops = getOpsLocked(uid, packageName, true); + if ((ops != null) && ops.isPrivileged) { + return false; + } + } + } if (userHandle == UserHandle.USER_OWNER) { if (uid != mDeviceOwnerUid) { return true; @@ -959,6 +980,27 @@ public class AppOpsService extends IAppOpsService.Stub { void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException, XmlPullParserException, IOException { int uid = Integer.parseInt(parser.getAttributeValue(null, "n")); + String isPrivilegedString = parser.getAttributeValue(null, "p"); + boolean isPrivileged = false; + if (isPrivilegedString == null) { + try { + IPackageManager packageManager = ActivityThread.getPackageManager(); + if (packageManager != null) { + ApplicationInfo appInfo = ActivityThread.getPackageManager() + .getApplicationInfo(pkgName, 0, UserHandle.getUserId(uid)); + if (appInfo != null) { + isPrivileged = (appInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0; + } + } else { + // Could not load data, don't add to cache so it will be loaded later. + return; + } + } catch (RemoteException e) { + Slog.w(TAG, "Could not contact PackageManager", e); + } + } else { + isPrivileged = Boolean.parseBoolean(isPrivilegedString); + } int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -993,7 +1035,7 @@ public class AppOpsService extends IAppOpsService.Stub { } Ops ops = pkgOps.get(pkgName); if (ops == null) { - ops = new Ops(pkgName, uid); + ops = new Ops(pkgName, uid, isPrivileged); pkgOps.put(pkgName, ops); } ops.put(op.op, op); @@ -1037,6 +1079,16 @@ public class AppOpsService extends IAppOpsService.Stub { } out.startTag(null, "uid"); out.attribute(null, "n", Integer.toString(pkg.getUid())); + synchronized (this) { + Ops ops = getOpsLocked(pkg.getUid(), pkg.getPackageName(), false); + // Should always be present as the list of PackageOps is generated + // from Ops. + if (ops != null) { + out.attribute(null, "p", Boolean.toString(ops.isPrivileged)); + } else { + out.attribute(null, "p", Boolean.toString(false)); + } + } List ops = pkg.getOps(); for (int j=0; j