Merge "Log AppOp access events in DiscreteRegistry" into main
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 991611a..b0791e3 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -257,3 +257,11 @@
description: "This flag is used to enable replacing permission BODY_SENSORS(and BODY_SENSORS_BACKGROUND) with granular health permission READ_HEART_RATE(and READ_HEALTH_DATA_IN_BACKGROUND)"
bug: "364638912"
}
+
+flag {
+ name: "appop_access_tracking_logging_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Enables logging of the AppOp access tracking"
+ bug: "365584286"
+}
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 430be03..314664b 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -110,7 +110,8 @@
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime,
- AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE,
+ DiscreteRegistry.ACCESS_TYPE_NOTE_OP);
}
/**
@@ -254,7 +255,7 @@
if (isStarted) {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
- attributionFlags, attributionChainId);
+ attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP);
}
}
@@ -290,12 +291,17 @@
* stopping in the HistoricalRegistry, but does not delete it.
*
* @param triggeredByUidStateChange If {@code true}, then this method operates as usual, except
- * that {@link AppOpsService#mActiveWatchers} will not be notified. This is currently only
- * used in {@link #onUidStateChanged(int)}, for the purpose of restarting (i.e.,
- * finishing then immediately starting again in the new uid state) the AttributedOp. In this
- * case, the caller is responsible for guaranteeing that either the AttributedOp is started
- * again or all {@link AppOpsService#mActiveWatchers} are notified that the AttributedOp is
- * finished.
+ * that {@link AppOpsService#mActiveWatchers} will not be
+ * notified. This is currently only
+ * used in {@link #onUidStateChanged(int)}, for the purpose of
+ * restarting (i.e.,
+ * finishing then immediately starting again in the new uid
+ * state) the AttributedOp. In this
+ * case, the caller is responsible for guaranteeing that either
+ * the AttributedOp is started
+ * again or all {@link AppOpsService#mActiveWatchers} are
+ * notified that the AttributedOp is
+ * finished.
*/
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
private void finishOrPause(@NonNull IBinder clientId, boolean triggeredByUidStateChange,
@@ -335,7 +341,9 @@
mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, event.getUidState(),
event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
- event.getAttributionFlags(), event.getAttributionChainId());
+ event.getAttributionFlags(), event.getAttributionChainId(),
+ isPausing ? DiscreteRegistry.ACCESS_TYPE_PAUSE_OP
+ : DiscreteRegistry.ACCESS_TYPE_FINISH_OP);
if (!isPausing) {
mAppOpsService.mInProgressStartOpEventPool.release(event);
@@ -443,7 +451,7 @@
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, event.getUidState(),
event.getFlags(), startTime, event.getAttributionFlags(),
- event.getAttributionChainId());
+ event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP);
if (shouldSendActive) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, tag, event.getVirtualDeviceId(), true,
@@ -864,12 +872,12 @@
}
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
- @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
+ @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath,
int proxyUid, @Nullable String proxyPackageName,
@Nullable String proxyAttributionTag, @Nullable String proxyDeviceId,
- @AppOpsManager.UidState int uidState,
- @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags
- int attributionFlags, int attributionChainId) throws RemoteException {
+ @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags,
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)
+ throws RemoteException {
InProgressStartOpEvent recycled = acquire();
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index 2ce4623..7f161f6 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -32,13 +32,23 @@
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
+import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS;
+import static android.app.AppOpsManager.OP_READ_ICC_SMS;
+import static android.app.AppOpsManager.OP_READ_SMS;
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING;
+import static android.app.AppOpsManager.OP_SEND_SMS;
+import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS;
+import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.AppOpsManager.OP_WRITE_ICC_SMS;
+import static android.app.AppOpsManager.OP_WRITE_SMS;
import static android.app.AppOpsManager.flagsToString;
import static android.app.AppOpsManager.getUidStateName;
import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
@@ -46,6 +56,7 @@
import static java.lang.Long.min;
import static java.lang.Math.max;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -62,6 +73,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -72,6 +84,8 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
@@ -125,7 +139,6 @@
* relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All
* outside calls are going through {@link HistoricalRegistry}, where
* {@link HistoricalRegistry#isPersistenceInitializedMLocked()} check is done.
- *
*/
final class DiscreteRegistry {
@@ -142,11 +155,40 @@
+ OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
+ OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+ "," + OP_RESERVED_FOR_TESTING;
+ private static final int[] sDiscreteOpsToLog =
+ new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA,
+ OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA,
+ OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS,
+ OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS,
+ OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION,
+ OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS,
+ };
private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
Duration.ofMinutes(1).toMillis();
+ static final int ACCESS_TYPE_NOTE_OP =
+ FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP;
+ static final int ACCESS_TYPE_START_OP =
+ FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP;
+ static final int ACCESS_TYPE_FINISH_OP =
+ FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP;
+ static final int ACCESS_TYPE_PAUSE_OP =
+ FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP;
+ static final int ACCESS_TYPE_RESUME_OP =
+ FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ACCESS_TYPE_"}, value = {
+ ACCESS_TYPE_NOTE_OP,
+ ACCESS_TYPE_START_OP,
+ ACCESS_TYPE_FINISH_OP,
+ ACCESS_TYPE_PAUSE_OP,
+ ACCESS_TYPE_RESUME_OP
+ })
+ public @interface AccessType {}
+
private static long sDiscreteHistoryCutoff;
private static long sDiscreteHistoryQuantization;
private static int[] sDiscreteOps;
@@ -255,7 +297,23 @@
void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op,
@Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
@AppOpsManager.UidState int uidState, long accessTime, long accessDuration,
- @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
+ @AccessType int accessType) {
+ if (shouldLogAccess(op)) {
+ int firstChar = 0;
+ if (attributionTag != null && attributionTag.startsWith(packageName)) {
+ firstChar = packageName.length();
+ if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar)
+ == '.') {
+ firstChar++;
+ }
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType,
+ uidState, flags, attributionFlags,
+ attributionTag == null ? null : attributionTag.substring(firstChar),
+ attributionChainId);
+ }
+
if (!isDiscreteOp(op, flags)) {
return;
}
@@ -388,7 +446,7 @@
if (event == null
|| event.mAttributionChainId == ATTRIBUTION_CHAIN_ID_NONE
|| (event.mAttributionFlags & ATTRIBUTION_FLAG_TRUSTED)
- == 0) {
+ == 0) {
continue;
}
@@ -1523,6 +1581,11 @@
return true;
}
+ private static boolean shouldLogAccess(int op) {
+ return Flags.appopAccessTrackingLoggingEnabled()
+ && ArrayUtils.contains(sDiscreteOpsToLog, op);
+ }
+
private static long discretizeTimeStamp(long timeStamp) {
return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
@@ -1530,7 +1593,7 @@
private static long discretizeDuration(long duration) {
return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1)
- / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
+ / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
}
void setDebugMode(boolean debugMode) {
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index fffb108..6b02538 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -474,7 +474,8 @@
void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
@NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
@OpFlags int flags, long accessTime,
- @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
+ @DiscreteRegistry.AccessType int accessType) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
@@ -487,7 +488,7 @@
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
attributionTag, flags, uidState, accessTime, -1, attributionFlags,
- attributionChainId);
+ attributionChainId, accessType);
}
}
}
@@ -510,7 +511,8 @@
void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
@NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
@OpFlags int flags, long eventStartTime, long increment,
- @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
+ @DiscreteRegistry.AccessType int accessType) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
@@ -522,7 +524,7 @@
attributionTag, uidState, flags, increment);
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
attributionTag, flags, uidState, eventStartTime, increment,
- attributionFlags, attributionChainId);
+ attributionFlags, attributionChainId, accessType);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java
index bc3a5ca..2ff0c62 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java
@@ -86,7 +86,8 @@
int attributionChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
- uidState, accessTime, duration, attributionFlags, attributionChainId);
+ uidState, accessTime, duration, attributionFlags, attributionChainId,
+ DiscreteRegistry.ACCESS_TYPE_FINISH_OP);
// Verify in-memory object is correct
fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
@@ -117,7 +118,8 @@
int attributionChainId = 10;
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
- uidState, accessTime, duration, attributionFlags, attributionChainId);
+ uidState, accessTime, duration, attributionFlags, attributionChainId,
+ DiscreteRegistry.ACCESS_TYPE_START_OP);
fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
duration, uidState, opFlags, attributionFlags, attributionChainId);