diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 257554207ef5d..92761b54e5f01 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -393,6 +393,8 @@ message Atom { WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"]; AppFreezeChanged app_freeze_changed = 254 [(module) = "framework"]; SnapshotMergeReported snapshot_merge_reported = 255; + ForegroundServiceAppOpSessionEnded foreground_service_app_op_session_ended = + 256 [(module) = "framework"]; SdkExtensionStatus sdk_extension_status = 354; } @@ -3285,12 +3287,12 @@ message OverlayStateChanged { ]; } -/* +/** * Logs foreground service starts and stops. * Note that this is not when a service starts or stops, but when it is * considered foreground. * Logged from - * //frameworks/base/services/core/java/com/android/server/am/ActiveServices.java + * frameworks/base/services/core/java/com/android/server/am/ActiveServices.java */ message ForegroundServiceStateChanged { optional int32 uid = 1 [(is_uid) = true]; @@ -3302,6 +3304,49 @@ message ForegroundServiceStateChanged { EXIT = 2; } optional State state = 3; + + // Whether the fgs is allowed while-in-use permissions, i.e. is considered 'in-use' to the user. + // (If the fgs was started while the app wasn't TOP it usually will be denied these permissions) + optional bool allow_while_in_use_permission = 4; +} + +/** + * Logs the number of times a uid accesses a sensitive AppOp during a foreground service session. + * A foreground service session is any continuous period during which the uid holds at least one + * foreground service; the atom will be pushed when the uid no longer holds any foreground services. + * Accesses initiated while the uid is in the TOP state are ignored. + * Sessions with no attempted accesses are not logged. + * Logged from + * frameworks/base/services/core/java/com/android/server/am/ActiveServices.java + */ +message ForegroundServiceAppOpSessionEnded { + optional int32 uid = 1 [(is_uid) = true]; + + // The operation's name. + // To the extent possible, preserve the mapping from AppOpsManager.OP_ constants. + // Only these named ops are actually logged. + enum AppOpName { + OP_NONE = -1; // Also represents UNKNOWN. + OP_COARSE_LOCATION = 0; + OP_FINE_LOCATION = 1; + OP_CAMERA = 26; + OP_RECORD_AUDIO = 27; + } + optional AppOpName app_op_name = 2 [default = OP_NONE]; + + // The uid's permission mode for accessing the AppOp during this fgs session. + enum Mode { + MODE_UNKNOWN = 0; + MODE_ALLOWED = 1; // Always allowed + MODE_IGNORED = 2; // Denied + MODE_FOREGROUND = 3; // Allow-while-in-use (or allowed-one-time) + } + optional Mode app_op_mode = 3; + + // Number of times this AppOp was requested and allowed. + optional int32 count_ops_accepted = 4; + // Number of times this AppOp was requested but denied. + optional int32 count_ops_rejected = 5; } /** diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 8f02f1555edfa..a53fc35080018 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -6910,11 +6910,7 @@ public class AppOpsManager { * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}. */ public int unsafeCheckOpRaw(@NonNull String op, int uid, @NonNull String packageName) { - try { - return mService.checkOperationRaw(strOpToOp(op), uid, packageName); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return unsafeCheckOpRawNoThrow(op, uid, packageName); } /** @@ -6923,8 +6919,17 @@ public class AppOpsManager { * {@link #MODE_FOREGROUND}. */ public int unsafeCheckOpRawNoThrow(@NonNull String op, int uid, @NonNull String packageName) { + return unsafeCheckOpRawNoThrow(strOpToOp(op), uid, packageName); + } + + /** + * Returns the raw mode associated with the op. + * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}. + * @hide + */ + public int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName) { try { - return mService.checkOperationRaw(strOpToOp(op), uid, packageName); + return mService.checkOperationRaw(op, uid, packageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 7f98c7f2ba85f..8ebbce3b2b057 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -36,6 +36,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE_E import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -88,11 +89,13 @@ import android.util.EventLog; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.webkit.WebViewZygote; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.app.procstats.ServiceState; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; @@ -177,6 +180,10 @@ public final class ActiveServices { /** Temporary list for holding the results of calls to {@link #collectPackageServicesLocked} */ private ArrayList mTmpCollectionResults = null; + /** Mapping from uid to their foreground service AppOpCallbacks (if they have one). */ + @GuardedBy("mAm") + private final SparseArray mFgsAppOpCallbacks = new SparseArray<>(); + /** * For keeping ActiveForegroundApps retaining state while the screen is off. */ @@ -1455,7 +1462,9 @@ public final class ActiveServices { null, true, false, ""); FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortInstanceName, - FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER); + FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, + r.mAllowWhileInUsePermissionInFgs); + registerAppOpCallbackLocked(r); mAm.updateForegroundServiceUsageStats(r.name, r.userId, true); } r.postNotification(); @@ -1504,9 +1513,11 @@ public final class ActiveServices { mAm.mAppOpsService.finishOperation( AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); + unregisterAppOpCallbackLocked(r); FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortInstanceName, - FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT); + FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT, + r.mAllowWhileInUsePermissionInFgs); mAm.updateForegroundServiceUsageStats(r.name, r.userId, false); if (r.app != null) { mAm.updateLruProcessLocked(r.app, false, null); @@ -1527,6 +1538,207 @@ public final class ActiveServices { } } + /** Registers an AppOpCallback for monitoring special AppOps for this foreground service. */ + private void registerAppOpCallbackLocked(@NonNull ServiceRecord r) { + if (r.app == null) { + return; + } + final int uid = r.appInfo.uid; + AppOpCallback callback = mFgsAppOpCallbacks.get(uid); + if (callback == null) { + callback = new AppOpCallback(r.app, mAm.getAppOpsManager()); + mFgsAppOpCallbacks.put(uid, callback); + } + callback.registerLocked(); + } + + /** Unregisters a foreground service's AppOpCallback. */ + private void unregisterAppOpCallbackLocked(@NonNull ServiceRecord r) { + final int uid = r.appInfo.uid; + final AppOpCallback callback = mFgsAppOpCallbacks.get(uid); + if (callback != null) { + callback.unregisterLocked(); + if (callback.isObsoleteLocked()) { + mFgsAppOpCallbacks.remove(uid); + } + } + } + + /** + * For monitoring when {@link #LOGGED_AP_OPS} AppOps occur by an app while it is holding + * at least one foreground service and is not also in the TOP state. + * Once the uid no longer holds any foreground services, this callback becomes stale + * (marked by {@link #isObsoleteLocked()}) and must no longer be used. + * + * Methods that end in Locked should only be called while the mAm lock is held. + */ + private static final class AppOpCallback { + /** AppOps that should be logged if they occur during a foreground service. */ + private static final int[] LOGGED_AP_OPS = new int[] { + AppOpsManager.OP_COARSE_LOCATION, + AppOpsManager.OP_FINE_LOCATION, + AppOpsManager.OP_RECORD_AUDIO, + AppOpsManager.OP_CAMERA + }; + + private final ProcessRecord mProcessRecord; + + /** Count of acceptances per appop (for LOGGED_AP_OPS) during this fgs session. */ + @GuardedBy("mCounterLock") + private final SparseIntArray mAcceptedOps = new SparseIntArray(); + /** Count of rejections per appop (for LOGGED_AP_OPS) during this fgs session. */ + @GuardedBy("mCounterLock") + private final SparseIntArray mRejectedOps = new SparseIntArray(); + + /** Lock for the purposes of mAcceptedOps and mRejectedOps. */ + private final Object mCounterLock = new Object(); + + /** + * AppOp Mode (e.g. {@link AppOpsManager#MODE_ALLOWED} per op. + * This currently cannot change without the process being killed, so they are constants. + */ + private final SparseIntArray mAppOpModes = new SparseIntArray(); + + /** + * Number of foreground services currently associated with this AppOpCallback (i.e. + * currently held for this uid). + */ + @GuardedBy("mAm") + private int mNumFgs = 0; + + /** + * Indicates that this Object is stale and must not be used. + * Specifically, when mNumFgs decreases down to 0, the callbacks will be unregistered and + * this AppOpCallback is unusable. + */ + @GuardedBy("mAm") + private boolean mDestroyed = false; + + private final AppOpsManager mAppOpsManager; + + AppOpCallback(@NonNull ProcessRecord r, @NonNull AppOpsManager appOpsManager) { + mProcessRecord = r; + mAppOpsManager = appOpsManager; + for (int op : LOGGED_AP_OPS) { + int mode = appOpsManager.unsafeCheckOpRawNoThrow(op, r.uid, r.info.packageName); + mAppOpModes.put(op, mode); + } + } + + private final AppOpsManager.OnOpNotedListener mOpNotedCallback = + new AppOpsManager.OnOpNotedListener() { + @Override + public void onOpNoted(int op, int uid, String pkgName, int result) { + if (uid == mProcessRecord.uid && isNotTop()) { + incrementOpCount(op, result == AppOpsManager.MODE_ALLOWED); + } + } + }; + + private final AppOpsManager.OnOpActiveChangedInternalListener mOpActiveCallback = + new AppOpsManager.OnOpActiveChangedInternalListener() { + @Override + public void onOpActiveChanged(int op, int uid, String pkgName, boolean active) { + if (uid == mProcessRecord.uid && active && isNotTop()) { + incrementOpCount(op, true); + } + } + }; + + private boolean isNotTop() { + return mProcessRecord.getCurProcState() != ActivityManager.PROCESS_STATE_TOP; + } + + private void incrementOpCount(int op, boolean allowed) { + synchronized (mCounterLock) { + final SparseIntArray counter = allowed ? mAcceptedOps : mRejectedOps; + final int index = counter.indexOfKey(op); + if (index < 0) { + counter.put(op, 1); + } else { + counter.setValueAt(index, counter.valueAt(index) + 1); + } + } + } + + void registerLocked() { + if (isObsoleteLocked()) { + Slog.wtf(TAG, "Trying to register on a stale AppOpCallback."); + return; + } + mNumFgs++; + if (mNumFgs == 1) { + mAppOpsManager.startWatchingNoted(LOGGED_AP_OPS, mOpNotedCallback); + mAppOpsManager.startWatchingActive(LOGGED_AP_OPS, mOpActiveCallback); + } + } + + void unregisterLocked() { + mNumFgs--; + if (mNumFgs <= 0) { + mDestroyed = true; + logFinalValues(); + mAppOpsManager.stopWatchingNoted(mOpNotedCallback); + mAppOpsManager.stopWatchingActive(mOpActiveCallback); + } + } + + /** + * Indicates that all foreground services for this uid are now over and the callback is + * stale and must never be used again. + */ + boolean isObsoleteLocked() { + return mDestroyed; + } + + private void logFinalValues() { + synchronized (mCounterLock) { + for (int op : LOGGED_AP_OPS) { + final int acceptances = mAcceptedOps.get(op); + final int rejections = mRejectedOps.get(op); + if (acceptances > 0 || rejections > 0) { + FrameworkStatsLog.write( + FrameworkStatsLog.FOREGROUND_SERVICE_APP_OP_SESSION_ENDED, + mProcessRecord.uid, opToEnum(op), + modeToEnum(mAppOpModes.get(op)), + acceptances, rejections + ); + } + } + } + } + + /** Maps AppOp mode to atoms.proto enum. */ + private static int modeToEnum(int mode) { + switch (mode) { + case AppOpsManager.MODE_ALLOWED: return FrameworkStatsLog + .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_ALLOWED; + case AppOpsManager.MODE_IGNORED: return FrameworkStatsLog + .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_IGNORED; + case AppOpsManager.MODE_FOREGROUND: return FrameworkStatsLog + .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_FOREGROUND; + default: return FrameworkStatsLog + .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_MODE__MODE_UNKNOWN; + } + } + } + + /** Maps AppOp op value to atoms.proto enum. */ + private static int opToEnum(int op) { + switch (op) { + case AppOpsManager.OP_COARSE_LOCATION: return FrameworkStatsLog + .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_COARSE_LOCATION; + case AppOpsManager.OP_FINE_LOCATION: return FrameworkStatsLog + .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_FINE_LOCATION; + case AppOpsManager.OP_CAMERA: return FrameworkStatsLog + .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_CAMERA; + case AppOpsManager.OP_RECORD_AUDIO: return FrameworkStatsLog + .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_RECORD_AUDIO; + default: return FrameworkStatsLog + .FOREGROUND_SERVICE_APP_OP_SESSION_ENDED__APP_OP_NAME__OP_NONE; + } + } + private void cancelForegroundNotificationLocked(ServiceRecord r) { if (r.foregroundId != 0) { // First check to see if this app has any other active foreground services @@ -3136,9 +3348,11 @@ public final class ActiveServices { mAm.mAppOpsService.finishOperation( AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); + unregisterAppOpCallbackLocked(r); FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortInstanceName, - FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT); + FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT, + r.mAllowWhileInUsePermissionInFgs); mAm.updateForegroundServiceUsageStats(r.name, r.userId, false); }