Merge changes from topics "presubmit-am-cd22d6400493475a97208d8e6980896b", "presubmit-am-d70f1b45fe9f4623abc8ce60634bd0c9" into tm-dev

* changes:
  Add exemptions for the apps with the camera/mic permissions granted
  Add bg exemption tracks for foreground only permissions
This commit is contained in:
Jing Ji
2022-03-18 20:54:38 +00:00
committed by Android (Google) Code Review
3 changed files with 335 additions and 43 deletions

View File

@@ -17,6 +17,14 @@
package com.android.server.am;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.app.AppOpsManager.OPSTR_CAMERA;
import static android.app.AppOpsManager.OPSTR_FINE_LOCATION;
import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.opToPublicName;
import static android.app.AppOpsManager.strOpToOp;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -27,28 +35,37 @@ import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNA
import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_PERMISSION;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.OnPermissionsChangedListener;
import android.content.pm.PackageManagerInternal;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.server.am.AppPermissionTracker.AppPermissionPolicy;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* The tracker for monitoring selected permission state of apps.
@@ -61,8 +78,17 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
private final MyHandler mHandler;
/**
* Keep a new instance of callback for each appop we're monitoring,
* as the AppOpsService doesn't support monitoring multiple appops with single callback
* instance (except the ALL_OPS case).
*/
@GuardedBy("mAppOpsCallbacks")
private final SparseArray<MyAppOpsCallback> mAppOpsCallbacks = new SparseArray<>();
@GuardedBy("mLock")
private SparseArray<ArraySet<String>> mUidGrantedPermissionsInMonitor = new SparseArray<>();
private SparseArray<ArraySet<UidGrantedPermissionState>> mUidGrantedPermissionsInMonitor =
new SparseArray<>();
private volatile boolean mLockedBootCompleted = false;
@@ -82,12 +108,25 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_CHANGED, uid, 0).sendToTarget();
}
private void handleAppOpsInit() {
final ArrayList<Integer> ops = new ArrayList<>();
final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
for (int i = 0; i < permissions.length; i++) {
final Pair<String, Integer> pair = permissions[i];
if (pair.second != OP_NONE) {
ops.add(pair.second);
}
}
startWatchingMode(ops.toArray(new Integer[ops.size()]));
}
private void handlePermissionsInit() {
final int[] allUsers = mInjector.getUserManagerInternal().getUserIds();
final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
final PermissionManagerServiceInternal pm = mInjector.getPermissionManagerServiceInternal();
final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
final SparseArray<ArraySet<String>> uidPerms = mUidGrantedPermissionsInMonitor;
final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
final SparseArray<ArraySet<UidGrantedPermissionState>> uidPerms =
mUidGrantedPermissionsInMonitor;
for (int userId : allUsers) {
final List<ApplicationInfo> apps = pmi.getInstalledApplications(0, userId, SYSTEM_UID);
if (apps == null) {
@@ -96,33 +135,44 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
final long now = SystemClock.elapsedRealtime();
for (int i = 0, size = apps.size(); i < size; i++) {
final ApplicationInfo ai = apps.get(i);
for (String permission : permissions) {
if (pm.checkUidPermission(ai.uid, permission) != PERMISSION_GRANTED) {
for (Pair<String, Integer> permission : permissions) {
final UidGrantedPermissionState state = new UidGrantedPermissionState(
ai.uid, permission.first, permission.second);
if (!state.isGranted()) {
// No need to track it.
continue;
}
synchronized (mLock) {
ArraySet<String> grantedPermissions = uidPerms.get(ai.uid);
ArraySet<UidGrantedPermissionState> grantedPermissions =
uidPerms.get(ai.uid);
if (grantedPermissions == null) {
grantedPermissions = new ArraySet<String>();
grantedPermissions = new ArraySet<UidGrantedPermissionState>();
uidPerms.put(ai.uid, grantedPermissions);
// This UID has at least one active permission-in-interest now,
// let the listeners know.
notifyListenersOnStateChange(ai.uid, DEFAULT_NAME, true, now,
STATE_TYPE_PERMISSION);
}
grantedPermissions.add(permission);
notifyListenersOnStateChange(ai.uid, DEFAULT_NAME, true, now,
STATE_TYPE_PERMISSION);
grantedPermissions.add(state);
}
}
}
}
}
private void handleAppOpsDestroy() {
stopWatchingMode();
}
private void handlePermissionsDestroy() {
synchronized (mLock) {
final SparseArray<ArraySet<String>> uidPerms = mUidGrantedPermissionsInMonitor;
final SparseArray<ArraySet<UidGrantedPermissionState>> uidPerms =
mUidGrantedPermissionsInMonitor;
final long now = SystemClock.elapsedRealtime();
for (int i = 0, size = uidPerms.size(); i < size; i++) {
final int uid = uidPerms.keyAt(i);
final ArraySet<String> grantedPermissions = uidPerms.valueAt(i);
for (int j = 0, numOfPerms = grantedPermissions.size(); j < numOfPerms; j++) {
final ArraySet<UidGrantedPermissionState> grantedPermissions = uidPerms.valueAt(i);
if (grantedPermissions.size() > 0) {
notifyListenersOnStateChange(uid, DEFAULT_NAME, false, now,
STATE_TYPE_PERMISSION);
}
@@ -131,44 +181,78 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
}
}
private void handleOpChanged(int op, int uid, String packageName) {
if (DEBUG_PERMISSION_TRACKER) {
final IAppOpsService appOpsService = mInjector.getIAppOpsService();
try {
final int mode = appOpsService.checkOperation(op, uid, packageName);
Slog.i(TAG, "onOpChanged: " + opToPublicName(op)
+ " " + UserHandle.formatUid(uid)
+ " " + packageName + " " + mode);
} catch (RemoteException e) {
// Intra-process call, should never happen.
}
}
final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
if (permissions != null && permissions.length > 0) {
for (int i = 0; i < permissions.length; i++) {
final Pair<String, Integer> pair = permissions[i];
if (pair.second != op) {
continue;
}
final UidGrantedPermissionState state =
new UidGrantedPermissionState(uid, pair.first, op);
synchronized (mLock) {
handlePermissionsChangedLocked(uid, new UidGrantedPermissionState[] {state});
}
break;
}
}
}
private void handlePermissionsChanged(int uid) {
final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
if (DEBUG_PERMISSION_TRACKER) {
Slog.i(TAG, "handlePermissionsChanged " + UserHandle.formatUid(uid));
}
final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
if (permissions != null && permissions.length > 0) {
final PermissionManagerServiceInternal pm =
mInjector.getPermissionManagerServiceInternal();
final boolean[] states = new boolean[permissions.length];
final UidGrantedPermissionState[] states =
new UidGrantedPermissionState[permissions.length];
for (int i = 0; i < permissions.length; i++) {
states[i] = pm.checkUidPermission(uid, permissions[i]) == PERMISSION_GRANTED;
final Pair<String, Integer> pair = permissions[i];
states[i] = new UidGrantedPermissionState(uid, pair.first, pair.second);
if (DEBUG_PERMISSION_TRACKER) {
Slog.i(TAG, UserHandle.formatUid(uid) + " " + permissions[i] + "=" + states[i]);
Slog.i(TAG, states[i].toString());
}
}
synchronized (mLock) {
handlePermissionsChangedLocked(uid, permissions, states);
handlePermissionsChangedLocked(uid, states);
}
}
}
@GuardedBy("mLock")
private void handlePermissionsChangedLocked(int uid, String[] permissions, boolean[] states) {
private void handlePermissionsChangedLocked(int uid, UidGrantedPermissionState[] states) {
final int index = mUidGrantedPermissionsInMonitor.indexOfKey(uid);
ArraySet<String> grantedPermissions = index >= 0
ArraySet<UidGrantedPermissionState> grantedPermissions = index >= 0
? mUidGrantedPermissionsInMonitor.valueAt(index) : null;
final long now = SystemClock.elapsedRealtime();
for (int i = 0; i < permissions.length; i++) {
final String permission = permissions[i];
final boolean granted = states[i];
for (int i = 0; i < states.length; i++) {
final boolean granted = states[i].isGranted();
boolean changed = false;
if (granted) {
if (grantedPermissions == null) {
grantedPermissions = new ArraySet<>();
mUidGrantedPermissionsInMonitor.put(uid, grantedPermissions);
changed = true;
}
changed = grantedPermissions.add(permission);
} else if (grantedPermissions != null) {
changed = grantedPermissions.remove(permission);
if (grantedPermissions.isEmpty()) {
grantedPermissions.add(states[i]);
} else if (grantedPermissions != null && !grantedPermissions.isEmpty()) {
if (grantedPermissions.remove(states[i]) && grantedPermissions.isEmpty()) {
mUidGrantedPermissionsInMonitor.removeAt(index);
changed = true;
}
}
if (changed) {
@@ -178,10 +262,141 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
}
}
/**
* Represents the grant state of a permission + appop of the given UID.
*/
private class UidGrantedPermissionState {
final int mUid;
final @Nullable String mPermission;
final int mAppOp;
private boolean mPermissionGranted;
private boolean mAppOpAllowed;
UidGrantedPermissionState(int uid, @Nullable String permission, int appOp) {
mUid = uid;
mPermission = permission;
mAppOp = appOp;
updatePermissionState();
updateAppOps();
}
void updatePermissionState() {
if (TextUtils.isEmpty(mPermission)) {
mPermissionGranted = true;
return;
}
mPermissionGranted = mInjector.getPermissionManagerServiceInternal()
.checkUidPermission(mUid, mPermission) == PERMISSION_GRANTED;
}
void updateAppOps() {
if (mAppOp == OP_NONE) {
mAppOpAllowed = true;
return;
}
final String[] packages = mInjector.getPackageManager().getPackagesForUid(mUid);
if (packages != null) {
final IAppOpsService appOpsService = mInjector.getIAppOpsService();
for (String pkg : packages) {
try {
final int mode = appOpsService.checkOperation(mAppOp, mUid, pkg);
if (mode == AppOpsManager.MODE_ALLOWED) {
mAppOpAllowed = true;
return;
}
} catch (RemoteException e) {
// Intra-process call, should never happen.
}
}
}
mAppOpAllowed = false;
}
boolean isGranted() {
return mPermissionGranted && mAppOpAllowed;
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof UidGrantedPermissionState)) {
return false;
}
final UidGrantedPermissionState otherState = (UidGrantedPermissionState) other;
return mUid == otherState.mUid && mAppOp == otherState.mAppOp
&& Objects.equals(mPermission, otherState.mPermission);
}
@Override
public int hashCode() {
return (Integer.hashCode(mUid) * 31 + Integer.hashCode(mAppOp)) * 31
+ (mPermission == null ? 0 : mPermission.hashCode());
}
@Override
public String toString() {
String s = "UidGrantedPermissionState{"
+ System.identityHashCode(this) + " "
+ UserHandle.formatUid(mUid) + ": ";
final boolean emptyPermissionName = TextUtils.isEmpty(mPermission);
if (!emptyPermissionName) {
s += mPermission + "=" + mPermissionGranted;
}
if (mAppOp != OP_NONE) {
if (!emptyPermissionName) {
s += ",";
}
s += opToPublicName(mAppOp) + "=" + mAppOpAllowed;
}
s += "}";
return s;
}
}
private void startWatchingMode(@NonNull Integer[] ops) {
synchronized (mAppOpsCallbacks) {
stopWatchingMode();
final IAppOpsService appOpsService = mInjector.getIAppOpsService();
try {
for (int op: ops) {
final MyAppOpsCallback cb = new MyAppOpsCallback();
mAppOpsCallbacks.put(op, cb);
appOpsService.startWatchingModeWithFlags(op, null,
AppOpsManager.WATCH_FOREGROUND_CHANGES, cb);
}
} catch (RemoteException e) {
// Intra-process call, should never happen.
}
}
}
private void stopWatchingMode() {
synchronized (mAppOpsCallbacks) {
final IAppOpsService appOpsService = mInjector.getIAppOpsService();
for (int i = mAppOpsCallbacks.size() - 1; i >= 0; i--) {
try {
appOpsService.stopWatchingMode(mAppOpsCallbacks.valueAt(i));
} catch (RemoteException e) {
// Intra-process call, should never happen.
}
}
mAppOpsCallbacks.clear();
}
}
private class MyAppOpsCallback extends IAppOpsCallback.Stub {
@Override
public void opChanged(int op, int uid, String packageName) {
mHandler.obtainMessage(MyHandler.MSG_APPOPS_CHANGED, op, uid, packageName)
.sendToTarget();
}
}
private static class MyHandler extends Handler {
static final int MSG_PERMISSIONS_INIT = 0;
static final int MSG_PERMISSIONS_DESTROY = 1;
static final int MSG_PERMISSIONS_CHANGED = 2;
static final int MSG_APPOPS_CHANGED = 3;
private @NonNull AppPermissionTracker mTracker;
@@ -194,14 +409,19 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_PERMISSIONS_INIT:
mTracker.handleAppOpsInit();
mTracker.handlePermissionsInit();
break;
case MSG_PERMISSIONS_DESTROY:
mTracker.handlePermissionsDestroy();
mTracker.handleAppOpsDestroy();
break;
case MSG_PERMISSIONS_CHANGED:
mTracker.handlePermissionsChanged(msg.arg1);
break;
case MSG_APPOPS_CHANGED:
mTracker.handleOpChanged(msg.arg1, msg.arg2, (String) msg.obj);
break;
}
}
}
@@ -231,25 +451,41 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.println("APP PERMISSIONS TRACKER:");
final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor();
final String prefixMore = " " + prefix;
final String prefixMoreMore = " " + prefixMore;
for (String permission : permissions) {
for (Pair<String, Integer> permission : permissions) {
pw.print(prefixMore);
pw.print(permission);
final boolean emptyPermissionName = TextUtils.isEmpty(permission.first);
if (!emptyPermissionName) {
pw.print(permission.first);
}
if (permission.second != OP_NONE) {
if (!emptyPermissionName) {
pw.print('+');
}
pw.print(opToPublicName(permission.second));
}
pw.println(':');
synchronized (mLock) {
final SparseArray<ArraySet<String>> uidPerms = mUidGrantedPermissionsInMonitor;
final SparseArray<ArraySet<UidGrantedPermissionState>> uidPerms =
mUidGrantedPermissionsInMonitor;
pw.print(prefixMoreMore);
pw.print('[');
boolean needDelimiter = false;
for (int i = 0, size = uidPerms.size(); i < size; i++) {
if (uidPerms.valueAt(i).contains(permission)) {
if (needDelimiter) {
pw.print(',');
final ArraySet<UidGrantedPermissionState> uidPerm = uidPerms.valueAt(i);
for (int j = uidPerm.size() - 1; j >= 0; j--) {
final UidGrantedPermissionState state = uidPerm.valueAt(j);
if (state.mAppOp == permission.second
&& TextUtils.equals(state.mPermission, permission.first)) {
if (needDelimiter) {
pw.print(',');
}
needDelimiter = true;
pw.print(UserHandle.formatUid(state.mUid));
break;
}
needDelimiter = true;
pw.print(UserHandle.formatUid(uidPerms.keyAt(i)));
}
}
pw.println(']');
@@ -277,20 +513,25 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
static final boolean DEFAULT_BG_PERMISSION_MONITOR_ENABLED = true;
/**
* Default value to {@link #mBgPermissionsInMonitor}.
* Default value to {@link #mBgPermissionsInMonitor}, it comes in pair;
* the first string strings in the pair is the permission name, and the second string
* is the appops name, if they are associated.
*/
static final String[] DEFAULT_BG_PERMISSIONS_IN_MONITOR = new String[] {
ACCESS_FINE_LOCATION,
ACCESS_FINE_LOCATION, OPSTR_FINE_LOCATION,
CAMERA, OPSTR_CAMERA,
RECORD_AUDIO, OPSTR_RECORD_AUDIO,
};
/**
* @see #KEY_BG_PERMISSIONS_IN_MONITOR.
*/
volatile String[] mBgPermissionsInMonitor = DEFAULT_BG_PERMISSIONS_IN_MONITOR;
volatile @NonNull Pair[] mBgPermissionsInMonitor;
AppPermissionPolicy(@NonNull Injector injector, @NonNull AppPermissionTracker tracker) {
super(injector, tracker, KEY_BG_PERMISSION_MONITOR_ENABLED,
DEFAULT_BG_PERMISSION_MONITOR_ENABLED);
mBgPermissionsInMonitor = parsePermissionConfig(DEFAULT_BG_PERMISSIONS_IN_MONITOR);
}
@Override
@@ -311,17 +552,38 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
}
}
String[] getBgPermissionsInMonitor() {
Pair[] getBgPermissionsInMonitor() {
return mBgPermissionsInMonitor;
}
private @NonNull Pair[] parsePermissionConfig(@NonNull String[] perms) {
final Pair[] result = new Pair[perms.length / 2];
for (int i = 0, j = 0; i < perms.length; i += 2, j++) {
try {
result[j] = Pair.create(TextUtils.isEmpty(perms[i]) ? null : perms[i],
TextUtils.isEmpty(perms[i + 1]) ? OP_NONE : strOpToOp(perms[i + 1]));
} catch (Exception e) {
// Ignore.
}
}
return result;
}
private void updateBgPermissionsInMonitor() {
final String config = DeviceConfig.getString(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_PERMISSIONS_IN_MONITOR,
null);
mBgPermissionsInMonitor = config != null
? config.split(",") : DEFAULT_BG_PERMISSIONS_IN_MONITOR;
final Pair[] newPermsInMonitor = parsePermissionConfig(
config != null ? config.split(",") : DEFAULT_BG_PERMISSIONS_IN_MONITOR);
if (!Arrays.equals(mBgPermissionsInMonitor, newPermsInMonitor)) {
mBgPermissionsInMonitor = newPermsInMonitor;
if (isEnabled()) {
// Trigger a reload.
onTrackerEnabled(false);
onTrackerEnabled(true);
}
}
}
@Override
@@ -338,7 +600,21 @@ final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy
pw.print(prefix);
pw.print(KEY_BG_PERMISSIONS_IN_MONITOR);
pw.print('=');
pw.println(Arrays.toString(mBgPermissionsInMonitor));
pw.print('[');
for (int i = 0; i < mBgPermissionsInMonitor.length; i++) {
if (i > 0) {
pw.print(',');
}
final Pair<String, Integer> pair = mBgPermissionsInMonitor[i];
if (pair.first != null) {
pw.print(pair.first);
}
pw.print(',');
if (pair.second != OP_NONE) {
pw.print(opToPublicName(pair.second));
}
}
pw.println(']');
}
}
}

View File

@@ -33,10 +33,12 @@ import android.media.session.MediaSessionManager;
import android.os.BatteryManagerInternal;
import android.os.BatteryStatsInternal;
import android.os.Handler;
import android.os.ServiceManager;
import android.permission.PermissionManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.app.IAppOpsService;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.notification.NotificationManagerInternal;
@@ -270,6 +272,7 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
MediaSessionManager mMediaSessionManager;
RoleManager mRoleManager;
NotificationManagerInternal mNotificationManagerInternal;
IAppOpsService mIAppOpsService;
void setPolicy(T policy) {
mAppStatePolicy = policy;
@@ -292,6 +295,8 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
mRoleManager = context.getSystemService(RoleManager.class);
mNotificationManagerInternal = LocalServices.getService(
NotificationManagerInternal.class);
mIAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
getPolicy().onSystemReady();
}
@@ -362,5 +367,9 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
NotificationManagerInternal getNotificationManagerInternal() {
return mNotificationManagerInternal;
}
IAppOpsService getIAppOpsService() {
return mIAppOpsService;
}
}
}

View File

@@ -119,6 +119,7 @@ import android.util.Pair;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.app.IAppOpsService;
import com.android.server.AppStateTracker;
import com.android.server.DeviceIdleInternal;
import com.android.server.am.AppBatteryExemptionTracker.AppBatteryExemptionPolicy;
@@ -231,6 +232,7 @@ public final class BackgroundRestrictionTest {
@Mock private MediaSessionManager mMediaSessionManager;
@Mock private RoleManager mRoleManager;
@Mock private TelephonyManager mTelephonyManager;
@Mock private IAppOpsService mIAppOpsService;
private long mCurrentTimeMillis;
@@ -2748,6 +2750,11 @@ public final class BackgroundRestrictionTest {
RoleManager getRoleManager() {
return BackgroundRestrictionTest.this.mRoleManager;
}
@Override
IAppOpsService getIAppOpsService() {
return BackgroundRestrictionTest.this.mIAppOpsService;
}
}
private class TestAppBatteryTrackerInjector extends TestBaseTrackerInjector<AppBatteryPolicy> {