diff options
3 files changed, 329 insertions, 43 deletions
diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java index 69f70ca0d0e0..8f4cb02af885 100644 --- a/services/core/java/com/android/server/am/AppPermissionTracker.java +++ b/services/core/java/com/android/server/am/AppPermissionTracker.java @@ -17,6 +17,10 @@ package com.android.server.am; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.app.AppOpsManager.OPSTR_FINE_LOCATION; +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 +31,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 +74,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 +104,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 +131,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 +177,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 +258,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 +405,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 +447,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 +509,23 @@ 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, }; /** * @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 +546,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 +594,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(']'); } } } diff --git a/services/core/java/com/android/server/am/BaseAppStateTracker.java b/services/core/java/com/android/server/am/BaseAppStateTracker.java index 0fada53d622e..61c7149d0dcd 100644 --- a/services/core/java/com/android/server/am/BaseAppStateTracker.java +++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java @@ -33,9 +33,11 @@ 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 com.android.internal.app.IAppOpsService; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.notification.NotificationManagerInternal; @@ -266,6 +268,7 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> { MediaSessionManager mMediaSessionManager; RoleManager mRoleManager; NotificationManagerInternal mNotificationManagerInternal; + IAppOpsService mIAppOpsService; void setPolicy(T policy) { mAppStatePolicy = policy; @@ -288,6 +291,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(); } @@ -358,5 +363,9 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> { NotificationManagerInternal getNotificationManagerInternal() { return mNotificationManagerInternal; } + + IAppOpsService getIAppOpsService() { + return mIAppOpsService; + } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index 053551309661..8bab6d68c20e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -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> { |