Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 932c698b authored by Manjeet Rulhania's avatar Manjeet Rulhania Committed by Android (Google) Code Review
Browse files

Merge "Report app op data with pushed atom" into main

parents fbabf522 df4cb666
Loading
Loading
Loading
Loading
+201 −10
Original line number Diff line number Diff line
@@ -22,9 +22,14 @@ import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
import static android.app.AppOpsManager.HISTORY_FLAG_AGGREGATE;
import static android.app.AppOpsManager.HISTORY_FLAG_DISCRETE;
import static android.app.AppOpsManager.MAX_PRIORITY_UID_STATE;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
import static android.app.AppOpsManager.flagsToString;
import static android.app.AppOpsManager.getUidStateName;

import static com.android.internal.util.FrameworkStatsLog.AGGREGATED_APP_OP_ACCESS_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_CACHE_FULL;
import static com.android.internal.util.FrameworkStatsLog.SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_MIGRATION;
import static com.android.internal.util.FrameworkStatsLog.SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_PERIODIC;
@@ -43,8 +48,11 @@ import android.os.Process;
import android.util.ArrayMap;
import android.util.IntArray;
import android.util.LongSparseArray;
import android.util.Pair;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ServiceThread;

import java.io.File;
@@ -93,6 +101,7 @@ public class AppOpHistoryHelper {
    private final Context mContext;
    private final AppOpHistoryDbHelper mDbHelper;
    private final SqliteWriteHandler mSqliteWriteHandler;
    private final MetricHandler mMetricHandler;
    private final AppOpHistoryCache mCache = new AppOpHistoryCache(1024);

    AppOpHistoryHelper(@NonNull Context context, File databaseFile,
@@ -105,6 +114,7 @@ public class AppOpHistoryHelper {
                new ServiceThread(TAG, Process.THREAD_PRIORITY_DEFAULT, true);
        thread.start();
        mSqliteWriteHandler = new SqliteWriteHandler(thread.getLooper());
        mMetricHandler = new MetricHandler(thread.getLooper());
    }

    // Set parameters before using this class.
@@ -223,6 +233,12 @@ public class AppOpHistoryHelper {
                opEvent.totalDurationMillis());
    }

    private void insertAppOpHistory(@NonNull List<AggregatedAppOpAccessEvent> appOpEvents,
            int writeSource) {
        mDbHelper.insertAppOpHistory(appOpEvents, writeSource);
        sendToMetricsHandler(appOpEvents);
    }

    private List<AggregatedAppOpAccessEvent> getAppOpHistory(AppOpsManager.HistoricalOps result,
            long beginTimeMillis, long endTimeMillis, int filter, int uidFilter,
            @Nullable String packageNameFilter,
@@ -231,10 +247,10 @@ public class AppOpHistoryHelper {
        IntArray opCodes = AppOpHistoryQueryHelper.getAppOpCodes(filter, opNamesFilter);
        // flush the cache into database before read.
        if (opCodes != null) {
            mDbHelper.insertAppOpHistory(mCache.evict(opCodes),
            insertAppOpHistory(mCache.evict(opCodes),
                    SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_READ);
        } else {
            mDbHelper.insertAppOpHistory(mCache.evictAll(),
            insertAppOpHistory(mCache.evictAll(),
                    SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_READ);
        }
        // Adjust begin & end time to time window's boundary.
@@ -259,8 +275,11 @@ public class AppOpHistoryHelper {

    void shutdown() {
        mSqliteWriteHandler.removeAllPendingMessages();
        mDbHelper.insertAppOpHistory(mCache.evictAll(),
        insertAppOpHistory(mCache.evictAll(),
                SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_SHUTDOWN);
        // Remove pending delayed message.
        mMetricHandler.removeMessages(MetricHandler.SEND_EVENTS);
        mMetricHandler.sendEmptyMessage(MetricHandler.SEND_EVENTS);
        mDbHelper.close();
    }

@@ -308,10 +327,10 @@ public class AppOpHistoryHelper {
        IntArray opCodes = AppOpHistoryQueryHelper.getAppOpCodes(filter, opNamesFilter);
        // flush the cache into database before read.
        if (opCodes != null) {
            mDbHelper.insertAppOpHistory(mCache.evict(opCodes),
            insertAppOpHistory(mCache.evict(opCodes),
                    SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_READ);
        } else {
            mDbHelper.insertAppOpHistory(mCache.evictAll(),
            insertAppOpHistory(mCache.evictAll(),
                    SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_READ);
        }
        // Adjust begin & end time to time window's boundary.
@@ -349,7 +368,7 @@ public class AppOpHistoryHelper {
            @AppOpsManager.HistoricalOpsRequestFilter int filter, @NonNull SimpleDateFormat sdf,
            @NonNull Date date, int limit) {
        // flush caches to the database
        mDbHelper.insertAppOpHistory(mCache.evictAll(),
        insertAppOpHistory(mCache.evictAll(),
                SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_READ);
        long currentTime = System.currentTimeMillis();
        long beginTimeMillis = discretizeTimestamp(currentTime - mHistoryRetentionMillis);
@@ -408,7 +427,7 @@ public class AppOpHistoryHelper {
            switch (msg.what) {
                case WRITE_DATABASE_PERIODIC -> {
                    try {
                        mDbHelper.insertAppOpHistory(mCache.evict(),
                        insertAppOpHistory(mCache.evict(),
                                SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_PERIODIC);
                    } finally {
                        ensurePeriodicJobsAreScheduled();
@@ -425,7 +444,7 @@ public class AppOpHistoryHelper {
                                evictedEvents.addAll(mCache.evictAll());
                            }
                        }
                        mDbHelper.insertAppOpHistory(evictedEvents,
                        insertAppOpHistory(evictedEvents,
                                SQLITE_APP_OP_EVENT_REPORTED__WRITE_TYPE__WRITE_CACHE_FULL);
                    } finally {
                        ensurePeriodicJobsAreScheduled();
@@ -451,6 +470,178 @@ public class AppOpHistoryHelper {
        }
    }

    void sendToMetricsHandler(@NonNull List<AggregatedAppOpAccessEvent> accessEvents) {
        Message msg = mMetricHandler.obtainMessage(MetricHandler.RECEIVE_EVENTS, accessEvents);
        mMetricHandler.sendMessage(msg);
    }

    private record AppOpMetricKey(
            @NonNull String packageName,
            int opCode,
            @Nullable String attributionTag
    ) {
    }

    private static class AppOpMetricValue {
        private long mForegroundDurationMillis;
        private int mForegroundAccessCount;
        private int mForegroundRejectCount;
        private long mBackgroundDurationMillis;
        private int mBackgroundAccessCount;
        private int mBackgroundRejectCount;

        AppOpMetricValue(
                long foregroundDurationMillis,
                int foregroundAccessCount,
                int foregroundRejectCount,
                long backgroundDurationMillis,
                int backgroundAccessCount,
                int backgroundRejectCount
        ) {
            this.mForegroundDurationMillis = foregroundDurationMillis;
            this.mForegroundAccessCount = foregroundAccessCount;
            this.mForegroundRejectCount = foregroundRejectCount;
            this.mBackgroundDurationMillis = backgroundDurationMillis;
            this.mBackgroundAccessCount = backgroundAccessCount;
            this.mBackgroundRejectCount = backgroundRejectCount;
        }

        void addForegroundDurationMillis(long foregroundDurationMillis) {
            this.mForegroundDurationMillis += foregroundDurationMillis;
        }

        void addForegroundAccessCount(int foregroundAccessCount) {
            this.mForegroundAccessCount += foregroundAccessCount;
        }

        void addForegroundRejectCount(int foregroundRejectCount) {
            this.mForegroundRejectCount += foregroundRejectCount;
        }

        void addBackgroundDurationMillis(long backgroundDurationMillis) {
            this.mBackgroundDurationMillis += backgroundDurationMillis;
        }

        void addBackgroundAccessCount(int backgroundAccessCount) {
            this.mBackgroundAccessCount += backgroundAccessCount;
        }

        void addBackgroundRejectCount(int backgroundRejectCount) {
            this.mBackgroundRejectCount += backgroundRejectCount;
        }
    }

    /**
     * This handler reports aggregated app op access events to StatsD.
     */
    private static class MetricHandler extends Handler {
        // Process events after 2 minutes once received by metrics handler.
        private static final long EVENT_PROCESSING_DELAY_MILLIS = Duration.ofMinutes(2).toMillis();
        // Process events immediately when reaching capacity limits.
        private static final int CACHE_CAPACITY = 128;
        private static final int OP_FLAGS_REPORTED = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED;
        static final int RECEIVE_EVENTS = 1;
        static final int SEND_EVENTS = 2;
        private final ArrayMap<AppOpMetricKey, AppOpMetricValue> mCache = new ArrayMap<>();

        MetricHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case RECEIVE_EVENTS -> {
                    List<AggregatedAppOpAccessEvent> events =
                            (List<AggregatedAppOpAccessEvent>) msg.obj;
                    if (events.isEmpty()) {
                        return;
                    }

                    synchronized (mCache) {
                        for (AggregatedAppOpAccessEvent accessEvent : events) {
                            if ((accessEvent.opFlags() & OP_FLAGS_REPORTED) == 0) {
                                continue;
                            }
                            AppOpMetricKey metricKey =
                                    new AppOpMetricKey(accessEvent.packageName(),
                                    accessEvent.opCode(), accessEvent.attributionTag());
                            boolean isUidStateInForeground =
                                    accessEvent.uidState() >= MAX_PRIORITY_UID_STATE
                                    && accessEvent.uidState() <= UID_STATE_MAX_LAST_NON_RESTRICTED;
                            long foregroundDurationMillis = 0;
                            int foregroundAccessCount = 0;
                            int foregroundRejectCount = 0;
                            long backgroundDurationMillis = 0;
                            int backgroundAccessCount = 0;
                            int backgroundRejectCount = 0;

                            if (isUidStateInForeground) {
                                foregroundDurationMillis = accessEvent.totalDurationMillis();
                                foregroundAccessCount = accessEvent.totalAccessCount();
                                foregroundRejectCount = accessEvent.totalRejectCount();
                            } else {
                                backgroundDurationMillis = accessEvent.totalDurationMillis();
                                backgroundAccessCount = accessEvent.totalAccessCount();
                                backgroundRejectCount = accessEvent.totalRejectCount();
                            }

                            AppOpMetricValue metricValue = mCache.get(metricKey);
                            if (metricValue != null) {
                                metricValue.addForegroundDurationMillis(foregroundDurationMillis);
                                metricValue.addForegroundAccessCount(foregroundAccessCount);
                                metricValue.addForegroundRejectCount(foregroundRejectCount);
                                metricValue.addBackgroundDurationMillis(backgroundDurationMillis);
                                metricValue.addBackgroundAccessCount(backgroundAccessCount);
                                metricValue.addBackgroundRejectCount(backgroundRejectCount);
                            } else {
                                mCache.put(metricKey, new AppOpMetricValue(
                                        foregroundDurationMillis,
                                        foregroundAccessCount,
                                        foregroundRejectCount,
                                        backgroundDurationMillis,
                                        backgroundAccessCount,
                                        backgroundRejectCount
                                ));
                            }
                        }

                        if (mCache.size() >= CACHE_CAPACITY) {
                            removeMessages(SEND_EVENTS); // Remove any pending delayed messages
                            sendEmptyMessage(SEND_EVENTS);
                        } else if (!hasMessages(SEND_EVENTS)) {
                            sendEmptyMessageDelayed(SEND_EVENTS, EVENT_PROCESSING_DELAY_MILLIS);
                        }
                    }
                }
                case SEND_EVENTS -> {
                    List<Pair<AppOpMetricKey, AppOpMetricValue>> events;
                    synchronized (mCache) {
                        if (mCache.isEmpty()) {
                            return;
                        }
                        events = new ArrayList<>(mCache.size());
                        for (Map.Entry<AppOpMetricKey, AppOpMetricValue> event: mCache.entrySet()) {
                            events.add(new Pair<>(event.getKey(), event.getValue()));
                        }
                        mCache.clear();
                    }
                    Slog.d(TAG, "MetricHandler reporting " + events.size() + " records.");
                    for (Pair<AppOpMetricKey, AppOpMetricValue> event : events) {
                        AppOpMetricKey metricKey = event.first;
                        AppOpMetricValue value = event.second;
                        FrameworkStatsLog.write(AGGREGATED_APP_OP_ACCESS_EVENT_REPORTED,
                                metricKey.packageName, metricKey.attributionTag,
                                metricKey.opCode, value.mForegroundAccessCount,
                                value.mBackgroundAccessCount, value.mForegroundRejectCount,
                                value.mBackgroundRejectCount, value.mForegroundDurationMillis,
                                value.mBackgroundDurationMillis);
                    }
                }
            }
        }
    }

    /**
     * A cache for aggregating app op accesses in a time window. Individual app op events
     * aren't stored on the disk, instead an aggregated events are persisted on the disk.