diff --git a/services/core/java/com/android/server/am/AppPermissionTracker.java b/services/core/java/com/android/server/am/AppPermissionTracker.java index 69f70ca0d0e08..622d7465d4bfc 100644 --- a/services/core/java/com/android/server/am/AppPermissionTracker.java +++ b/services/core/java/com/android/server/am/AppPermissionTracker.java @@ -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 mAppOpsCallbacks = new SparseArray<>(); + @GuardedBy("mLock") - private SparseArray> mUidGrantedPermissionsInMonitor = new SparseArray<>(); + private SparseArray> mUidGrantedPermissionsInMonitor = + new SparseArray<>(); private volatile boolean mLockedBootCompleted = false; @@ -82,12 +108,25 @@ final class AppPermissionTracker extends BaseAppStateTracker ops = new ArrayList<>(); + final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + for (int i = 0; i < permissions.length; i++) { + final Pair 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> uidPerms = mUidGrantedPermissionsInMonitor; + final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + final SparseArray> uidPerms = + mUidGrantedPermissionsInMonitor; for (int userId : allUsers) { final List apps = pmi.getInstalledApplications(0, userId, SYSTEM_UID); if (apps == null) { @@ -96,33 +135,44 @@ final class AppPermissionTracker extends BaseAppStateTracker 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 grantedPermissions = uidPerms.get(ai.uid); + ArraySet grantedPermissions = + uidPerms.get(ai.uid); if (grantedPermissions == null) { - grantedPermissions = new ArraySet(); + grantedPermissions = new ArraySet(); 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> uidPerms = mUidGrantedPermissionsInMonitor; + final SparseArray> 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 grantedPermissions = uidPerms.valueAt(i); - for (int j = 0, numOfPerms = grantedPermissions.size(); j < numOfPerms; j++) { + final ArraySet 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 0) { + for (int i = 0; i < permissions.length; i++) { + final Pair 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 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 grantedPermissions = index >= 0 + ArraySet 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= 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 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> uidPerms = mUidGrantedPermissionsInMonitor; + final SparseArray> 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 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 0) { + pw.print(','); + } + final Pair 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 daff4c1c6424f..570d7e53fadc3 100644 --- a/services/core/java/com/android/server/am/BaseAppStateTracker.java +++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java @@ -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 { MediaSessionManager mMediaSessionManager; RoleManager mRoleManager; NotificationManagerInternal mNotificationManagerInternal; + IAppOpsService mIAppOpsService; void setPolicy(T policy) { mAppStatePolicy = policy; @@ -292,6 +295,8 @@ public abstract class BaseAppStateTracker { 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 { 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 053551309661f..8bab6d68c20e7 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 {