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);