Loading services/core/java/com/android/server/appop/AppOpHistoryDbHelper.java +10 −4 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteRawStatement; import android.os.Trace; import android.util.IntArray; import android.util.Slog; Loading Loading @@ -81,7 +82,8 @@ class AppOpHistoryDbHelper extends SQLiteOpenHelper { if (appOpEvents.isEmpty()) { return; } Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "AppOpHistoryDbHelper_" + mAggregationTimeWindow + "_Write"); try { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); Loading Loading @@ -118,7 +120,8 @@ class AppOpHistoryDbHelper extends SQLiteOpenHelper { event.totalRejectCount()); statement.step(); } catch (Exception exception) { Slog.e(LOG_TAG, "Couldn't insert app op event: " + event, exception); Slog.e(LOG_TAG, "Couldn't insert app op event: " + event + ", database " + mDatabaseFile.getName(), exception); } finally { statement.reset(); } Loading @@ -128,12 +131,15 @@ class AppOpHistoryDbHelper extends SQLiteOpenHelper { try { db.endTransaction(); } catch (SQLiteException exception) { Slog.e(LOG_TAG, "Couldn't commit transaction when inserting app ops, database" + " file size (bytes) : " + mDatabaseFile.length(), exception); Slog.e(LOG_TAG, "Couldn't commit transaction inserting app ops, database" + mDatabaseFile.getName() + ", file size (bytes) : " + mDatabaseFile.length(), exception); } } } catch (Exception ex) { Slog.e(LOG_TAG, "Couldn't insert app op records in " + mDatabaseFile.getName(), ex); } finally { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } } Loading services/core/java/com/android/server/appop/AppOpHistoryHelper.java +49 −39 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import java.util.Date; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; /** Loading @@ -65,19 +66,21 @@ import java.util.Set; */ public class AppOpHistoryHelper { private static final String TAG = "AppOpHistoryHelper"; private static final long PERIODIC_JOB_MAX_VARIATION_MILLIS = Duration.ofMinutes(1).toMillis(); private static final long DB_WRITE_INTERVAL_PERIODIC_MILLIS = Duration.ofMinutes(10).toMillis(); private static final long EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS = Duration.ofHours(6).toMillis(); // Event type handled by SqliteWriteHandler private static final int WRITE_DATABASE_PERIODIC = 1; private static final int DELETE_EXPIRED_ENTRIES = 2; private static final int DELETE_EXPIRED_ENTRIES_PERIODIC = 2; private static final int WRITE_DATABASE_CACHE_FULL = 3; // Used in adding variation to periodic job interval private final Random mRandom = new Random(); // time window interval for aggregation private long mQuantizationMillis; private long mHistoryRetentionMillis; private final File mDatabaseFileName; private final File mDatabaseFile; private final Context mContext; private final AppOpHistoryDbHelper mDbHelper; private final SqliteWriteHandler mSqliteWriteHandler; Loading @@ -86,7 +89,7 @@ public class AppOpHistoryHelper { AppOpHistoryHelper(@NonNull Context context, File databaseFile, AggregationTimeWindow aggregationTimeWindow, int databaseVersion) { mContext = context; mDatabaseFileName = databaseFile; mDatabaseFile = databaseFile; mDbHelper = new AppOpHistoryDbHelper( context, databaseFile, aggregationTimeWindow, databaseVersion); ServiceThread thread = Loading @@ -99,10 +102,8 @@ public class AppOpHistoryHelper { void systemReady(long quantizationMillis, long historyRetentionMillis) { mQuantizationMillis = quantizationMillis; mHistoryRetentionMillis = historyRetentionMillis; mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC, DB_WRITE_INTERVAL_PERIODIC_MILLIS); mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES, EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS); ensurePeriodicJobsAreScheduled(); } void incrementOpAccessedCount(int op, int uid, @NonNull String packageName, Loading Loading @@ -225,9 +226,9 @@ public class AppOpHistoryHelper { attributionTagFilter, opCodes, opFlagsFilter, -1, null, false); } void deleteDatabase() { boolean deleteDatabase() { mDbHelper.close(); mContext.deleteDatabase(mDatabaseFileName.getAbsolutePath()); return mContext.deleteDatabase(mDatabaseFile.getAbsolutePath()); } long getLargestAttributionChainId() { Loading Loading @@ -356,25 +357,33 @@ public class AppOpHistoryHelper { pw.println(); } private void ensurePeriodicJobsAreScheduled() { if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_PERIODIC)) { mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC, DB_WRITE_INTERVAL_PERIODIC_MILLIS + mRandom.nextLong(0, PERIODIC_JOB_MAX_VARIATION_MILLIS)); } if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES_PERIODIC)) { mSqliteWriteHandler.sendEmptyMessageDelayed( DELETE_EXPIRED_ENTRIES_PERIODIC, EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS + mRandom.nextLong(0, PERIODIC_JOB_MAX_VARIATION_MILLIS)); } } private class SqliteWriteHandler extends Handler { SqliteWriteHandler(Looper looper) { super(looper); } @Override public void handleMessage(@androidx.annotation.NonNull Message msg) { public void handleMessage(@NonNull Message msg) { switch (msg.what) { case WRITE_DATABASE_PERIODIC -> { try { mDbHelper.insertAppOpHistory(mCache.evict()); } finally { mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC, DB_WRITE_INTERVAL_PERIODIC_MILLIS); // Schedule a cleanup to truncate older (before cutoff time) entries. if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES)) { mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES, EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS); } ensurePeriodicJobsAreScheduled(); } } case WRITE_DATABASE_CACHE_FULL -> { Loading @@ -390,37 +399,38 @@ public class AppOpHistoryHelper { } mDbHelper.insertAppOpHistory(evictedEvents); } finally { // Just in case initial message is not scheduled. if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_PERIODIC)) { mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC, DB_WRITE_INTERVAL_PERIODIC_MILLIS); } ensurePeriodicJobsAreScheduled(); } } case DELETE_EXPIRED_ENTRIES -> { case DELETE_EXPIRED_ENTRIES_PERIODIC -> { try { long cutOffTimeStamp = System.currentTimeMillis() - mHistoryRetentionMillis; mDbHelper.execSQL( AppOpHistoryTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME, new Object[]{cutOffTimeStamp}); } finally { ensurePeriodicJobsAreScheduled(); } } } } void removeAllPendingMessages() { removeMessages(WRITE_DATABASE_PERIODIC); removeMessages(DELETE_EXPIRED_ENTRIES); removeMessages(DELETE_EXPIRED_ENTRIES_PERIODIC); removeMessages(WRITE_DATABASE_CACHE_FULL); } } /** * A cache for aggregating app op access counts for a time window. Individual app op events * aren't stored on the disk, instead an aggregated event is persisted on the disk. * 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. * * <p> * These events are persisted into sqlite database * 1) Periodic interval. * 2) When the cache become full. * 3) During read call, flush the whole cache to disk. * 3) During read call, flush the cache to disk to make read simpler. * 4) During shutdown. */ class AppOpHistoryCache { Loading @@ -441,7 +451,7 @@ public class AppOpHistoryHelper { * @param rejectCount Reject counts to be aggregated for an event. * @param duration Access duration to be aggregated for an event. */ public void insertOrUpdate(AppOpAccessEvent accessKey, int accessCount, private void insertOrUpdate(AppOpAccessEvent accessKey, int accessCount, int rejectCount, long duration) { synchronized (this) { AggregatedAppOpValues appOpAccessValue = mCache.get(accessKey); Loading Loading @@ -500,7 +510,7 @@ public class AppOpHistoryHelper { /** * Evict specified app ops from cache, and return the list of evicted ops. */ public List<AggregatedAppOpAccessEvent> evict(IntArray ops) { private List<AggregatedAppOpAccessEvent> evict(IntArray ops) { synchronized (this) { List<AggregatedAppOpAccessEvent> cachedOps = new ArrayList<>(); List<AppOpAccessEvent> keysToBeRemoved = new ArrayList<>(); Loading Loading @@ -528,7 +538,7 @@ public class AppOpHistoryHelper { * * @return return all removed entries. */ public List<AggregatedAppOpAccessEvent> evictAll() { private List<AggregatedAppOpAccessEvent> evictAll() { synchronized (this) { List<AggregatedAppOpAccessEvent> cachedOps = snapshot(); mCache.clear(); Loading @@ -539,13 +549,13 @@ public class AppOpHistoryHelper { /** * Remove all entries from the cache. */ public void clear() { private void clear() { synchronized (this) { mCache.clear(); } } public List<AggregatedAppOpAccessEvent> snapshot() { private List<AggregatedAppOpAccessEvent> snapshot() { List<AggregatedAppOpAccessEvent> events = new ArrayList<>(); synchronized (this) { for (Map.Entry<AppOpAccessEvent, AggregatedAppOpValues> event : Loading @@ -557,7 +567,7 @@ public class AppOpHistoryHelper { } /** Remove cached events for given UID and package. */ public void clear(int uid, String packageName) { private void clear(int uid, String packageName) { synchronized (this) { List<AppOpAccessEvent> keysToBeDeleted = new ArrayList<>(); for (Map.Entry<AppOpAccessEvent, AggregatedAppOpValues> event : Loading services/core/java/com/android/server/appop/AppOpHistoryTable.java +3 −2 Original line number Diff line number Diff line Loading @@ -18,8 +18,9 @@ package com.android.server.appop; /** * SQLite table for storing aggregated app op access events. * Each row summarizes the count of accessed and rejected events that occurred within a specific * time window (e.g., 1 or 15 minutes). This aggregation helps in efficiently storing the app op * Each row summarizes the count of accessed, rejected events, and duration sum that occurred * within a specific time window (e.g., 1 or 15 minutes). This aggregation helps in efficiently * storing the app op * access events. * * <p>The following columns form a composite key to uniquely identify an aggregated record Loading services/core/java/com/android/server/appop/AppOpsService.java +0 −1 Original line number Diff line number Diff line Loading @@ -6360,7 +6360,6 @@ public class AppOpsService extends IAppOpsService.Stub { pw.println("Wrong parameter: " + args[i]); return; } includeDiscreteOps = true; } else if ("--history".equals(arg)) { dumpHistory = true; } else if (arg.length() > 0 && arg.charAt(0) == '-') { Loading services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java +92 −53 Original line number Diff line number Diff line Loading @@ -16,66 +16,83 @@ package com.android.server.appop; import android.annotation.NonNull; import android.util.Slog; import java.util.ArrayList; import java.util.List; /** * Helper class for migrating discrete ops from xml to sqlite * Helper class for migrating discrete ops from xml to sqlite or vice versa. */ public class DiscreteOpsMigrationHelper { private static final String LOG_TAG = "DiscreteOpsMigration"; /** * migrate discrete ops from xml to sqlite. */ static void migrateFromXmlToSqlite(DiscreteOpsXmlRegistry sourceRegistry, DiscreteOpsSqlRegistry targetRegistry) { try { DiscreteOpsXmlRegistry.DiscreteOps xmlOps = sourceRegistry.getAllDiscreteOps(); List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps(xmlOps); List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps( xmlOps); targetRegistry.migrateDiscreteAppOpHistory(discreteOps, xmlOps.mChainIdOffset); if (!sourceRegistry.deleteDiscreteOpsDir()) { Slog.w(LOG_TAG, "Couldn't delete appops xml directories."); } } catch (Exception ex) { Slog.e(LOG_TAG, "migrateFromXmlToSqlite failed.", ex); sourceRegistry.deleteDiscreteOpsDir(); } } /** * migrate discrete ops from xml to unified sqlite schema. */ static void migrateFromXmlToUnifiedSchemaSqlite(DiscreteOpsXmlRegistry sourceRegistry, AppOpHistoryHelper targetRegistry) { try { DiscreteOpsXmlRegistry.DiscreteOps xmlOps = sourceRegistry.getAllDiscreteOps(); List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps(xmlOps); List<AggregatedAppOpAccessEvent> convertedOps = new ArrayList<>(); for (DiscreteOpsSqlRegistry.DiscreteOp event: discreteOps) { convertedOps.add(new AggregatedAppOpAccessEvent(event.getUid(), event.getPackageName(), event.getOpCode(), event.getDeviceId(), event.getAttributionTag(), event.getOpFlags(), event.getUidState(), event.getAttributionFlags(), event.getChainId(), event.getAccessTime(), event.getDuration(), 0, 1, 0)); } List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps( xmlOps); List<AggregatedAppOpAccessEvent> convertedOps = getAggregatedAppOpAccessEvents(discreteOps); targetRegistry.migrateDiscreteAppOpHistory(convertedOps); if (!sourceRegistry.deleteDiscreteOpsDir()) { Slog.w(LOG_TAG, "Couldn't delete appops xml directories."); } } catch (Exception ex) { Slog.e(LOG_TAG, "migrateFromXmlToUnifiedSchemaSqlite failed.", ex); sourceRegistry.deleteDiscreteOpsDir(); } } /** * migrate discrete ops from sqlite to unified-schema sqlite. */ static void migrateFromSqliteToUnifiedSchemaSqlite(DiscreteOpsSqlRegistry sourceRegistry, AppOpHistoryHelper targetRegistry) { try { List<DiscreteOpsSqlRegistry.DiscreteOp> sourceOps = sourceRegistry.getAllDiscreteOps(); List<AggregatedAppOpAccessEvent> convertedOps = new ArrayList<>(); for (DiscreteOpsSqlRegistry.DiscreteOp event: sourceOps) { convertedOps.add(new AggregatedAppOpAccessEvent(event.getUid(), event.getPackageName(), event.getOpCode(), event.getDeviceId(), event.getAttributionTag(), event.getOpFlags(), event.getUidState(), event.getAttributionFlags(), event.getChainId(), event.getAccessTime(), event.getDuration(), 0, 1, 0)); } List<AggregatedAppOpAccessEvent> convertedOps = getAggregatedAppOpAccessEvents(sourceOps); targetRegistry.migrateDiscreteAppOpHistory(convertedOps); if (!sourceRegistry.deleteDatabase()) { Slog.w(LOG_TAG, "Couldn't delete appops sql database."); } } catch (Exception ex) { Slog.e(LOG_TAG, "migrateFromSqliteToUnifiedSchemaSqlite failed.", ex); sourceRegistry.deleteDatabase(); } } /** * rollback discrete ops from unified schema sqlite to sqlite schema. */ static void rollbackFromUnifiedSchemaSqliteToSqlite(AppOpHistoryHelper sourceRegistry, DiscreteOpsSqlRegistry targetRegistry) { try { List<AggregatedAppOpAccessEvent> unifiedSchemaSqliteOps = sourceRegistry.getAppOpHistory(); List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = new ArrayList<>(); Loading @@ -91,8 +108,13 @@ public class DiscreteOpsMigrationHelper { largestChainId = Math.max(largestChainId, event.attributionChainId()); } targetRegistry.migrateDiscreteAppOpHistory(discreteOps, largestChainId); if (!sourceRegistry.deleteDatabase()) { Slog.w(LOG_TAG, "Couldn't delete appops unified sql database."); } } catch (Exception ex) { Slog.e(LOG_TAG, "rollbackFromUnifiedSchemaSqliteToSqlite failed.", ex); sourceRegistry.deleteDatabase(); sourceRegistry.shutdown(); } } /** Loading @@ -100,24 +122,23 @@ public class DiscreteOpsMigrationHelper { */ static void rollbackFromSqliteToXml(DiscreteOpsSqlRegistry sourceRegistry, DiscreteOpsXmlRegistry targetRegistry) { try { List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sourceRegistry.getAllDiscreteOps(); // Only migrate configured discrete ops. Sqlite may contain all runtime ops, and more. List<DiscreteOpsSqlRegistry.DiscreteOp> filteredList = new ArrayList<>(); for (DiscreteOpsSqlRegistry.DiscreteOp opEvent: sqlOps) { if (DiscreteOpsRegistry.isDiscreteOp(opEvent.getOpCode(), opEvent.getOpFlags())) { filteredList.add(opEvent); } } DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(filteredList); DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(sqlOps); targetRegistry.migrateDiscreteAppOpHistory(xmlOps); if (!sourceRegistry.deleteDatabase()) { Slog.w(LOG_TAG, "Couldn't delete appops sql database."); } } catch (Exception ex) { Slog.e(LOG_TAG, "rollbackFromSqliteToXml failed.", ex); sourceRegistry.deleteDatabase(); } } /** * Convert sqlite flat rows to hierarchical data. */ @NonNull private static DiscreteOpsXmlRegistry.DiscreteOps getXmlDiscreteOps( List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps) { DiscreteOpsXmlRegistry.DiscreteOps xmlOps = Loading @@ -140,6 +161,7 @@ public class DiscreteOpsMigrationHelper { /** * Convert xml (hierarchical) data to flat row based data. */ @NonNull private static List<DiscreteOpsSqlRegistry.DiscreteOp> convertXmlToSqlDiscreteOps( DiscreteOpsXmlRegistry.DiscreteOps discreteOps) { List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents = new ArrayList<>(); Loading Loading @@ -173,4 +195,21 @@ public class DiscreteOpsMigrationHelper { return opEvents; } @NonNull private static List<AggregatedAppOpAccessEvent> getAggregatedAppOpAccessEvents( List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps) { List<AggregatedAppOpAccessEvent> convertedOps = new ArrayList<>(); for (DiscreteOpsSqlRegistry.DiscreteOp event : discreteOps) { convertedOps.add( new AggregatedAppOpAccessEvent(event.getUid(), event.getPackageName(), event.getOpCode(), event.getDeviceId(), event.getAttributionTag(), event.getOpFlags(), event.getUidState(), event.getAttributionFlags(), event.getChainId(), event.getAccessTime(), event.getDuration(), 0, 1, 0)); } return convertedOps; } } Loading
services/core/java/com/android/server/appop/AppOpHistoryDbHelper.java +10 −4 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteRawStatement; import android.os.Trace; import android.util.IntArray; import android.util.Slog; Loading Loading @@ -81,7 +82,8 @@ class AppOpHistoryDbHelper extends SQLiteOpenHelper { if (appOpEvents.isEmpty()) { return; } Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "AppOpHistoryDbHelper_" + mAggregationTimeWindow + "_Write"); try { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); Loading Loading @@ -118,7 +120,8 @@ class AppOpHistoryDbHelper extends SQLiteOpenHelper { event.totalRejectCount()); statement.step(); } catch (Exception exception) { Slog.e(LOG_TAG, "Couldn't insert app op event: " + event, exception); Slog.e(LOG_TAG, "Couldn't insert app op event: " + event + ", database " + mDatabaseFile.getName(), exception); } finally { statement.reset(); } Loading @@ -128,12 +131,15 @@ class AppOpHistoryDbHelper extends SQLiteOpenHelper { try { db.endTransaction(); } catch (SQLiteException exception) { Slog.e(LOG_TAG, "Couldn't commit transaction when inserting app ops, database" + " file size (bytes) : " + mDatabaseFile.length(), exception); Slog.e(LOG_TAG, "Couldn't commit transaction inserting app ops, database" + mDatabaseFile.getName() + ", file size (bytes) : " + mDatabaseFile.length(), exception); } } } catch (Exception ex) { Slog.e(LOG_TAG, "Couldn't insert app op records in " + mDatabaseFile.getName(), ex); } finally { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } } Loading
services/core/java/com/android/server/appop/AppOpHistoryHelper.java +49 −39 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import java.util.Date; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; /** Loading @@ -65,19 +66,21 @@ import java.util.Set; */ public class AppOpHistoryHelper { private static final String TAG = "AppOpHistoryHelper"; private static final long PERIODIC_JOB_MAX_VARIATION_MILLIS = Duration.ofMinutes(1).toMillis(); private static final long DB_WRITE_INTERVAL_PERIODIC_MILLIS = Duration.ofMinutes(10).toMillis(); private static final long EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS = Duration.ofHours(6).toMillis(); // Event type handled by SqliteWriteHandler private static final int WRITE_DATABASE_PERIODIC = 1; private static final int DELETE_EXPIRED_ENTRIES = 2; private static final int DELETE_EXPIRED_ENTRIES_PERIODIC = 2; private static final int WRITE_DATABASE_CACHE_FULL = 3; // Used in adding variation to periodic job interval private final Random mRandom = new Random(); // time window interval for aggregation private long mQuantizationMillis; private long mHistoryRetentionMillis; private final File mDatabaseFileName; private final File mDatabaseFile; private final Context mContext; private final AppOpHistoryDbHelper mDbHelper; private final SqliteWriteHandler mSqliteWriteHandler; Loading @@ -86,7 +89,7 @@ public class AppOpHistoryHelper { AppOpHistoryHelper(@NonNull Context context, File databaseFile, AggregationTimeWindow aggregationTimeWindow, int databaseVersion) { mContext = context; mDatabaseFileName = databaseFile; mDatabaseFile = databaseFile; mDbHelper = new AppOpHistoryDbHelper( context, databaseFile, aggregationTimeWindow, databaseVersion); ServiceThread thread = Loading @@ -99,10 +102,8 @@ public class AppOpHistoryHelper { void systemReady(long quantizationMillis, long historyRetentionMillis) { mQuantizationMillis = quantizationMillis; mHistoryRetentionMillis = historyRetentionMillis; mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC, DB_WRITE_INTERVAL_PERIODIC_MILLIS); mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES, EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS); ensurePeriodicJobsAreScheduled(); } void incrementOpAccessedCount(int op, int uid, @NonNull String packageName, Loading Loading @@ -225,9 +226,9 @@ public class AppOpHistoryHelper { attributionTagFilter, opCodes, opFlagsFilter, -1, null, false); } void deleteDatabase() { boolean deleteDatabase() { mDbHelper.close(); mContext.deleteDatabase(mDatabaseFileName.getAbsolutePath()); return mContext.deleteDatabase(mDatabaseFile.getAbsolutePath()); } long getLargestAttributionChainId() { Loading Loading @@ -356,25 +357,33 @@ public class AppOpHistoryHelper { pw.println(); } private void ensurePeriodicJobsAreScheduled() { if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_PERIODIC)) { mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC, DB_WRITE_INTERVAL_PERIODIC_MILLIS + mRandom.nextLong(0, PERIODIC_JOB_MAX_VARIATION_MILLIS)); } if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES_PERIODIC)) { mSqliteWriteHandler.sendEmptyMessageDelayed( DELETE_EXPIRED_ENTRIES_PERIODIC, EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS + mRandom.nextLong(0, PERIODIC_JOB_MAX_VARIATION_MILLIS)); } } private class SqliteWriteHandler extends Handler { SqliteWriteHandler(Looper looper) { super(looper); } @Override public void handleMessage(@androidx.annotation.NonNull Message msg) { public void handleMessage(@NonNull Message msg) { switch (msg.what) { case WRITE_DATABASE_PERIODIC -> { try { mDbHelper.insertAppOpHistory(mCache.evict()); } finally { mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC, DB_WRITE_INTERVAL_PERIODIC_MILLIS); // Schedule a cleanup to truncate older (before cutoff time) entries. if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES)) { mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES, EXPIRED_ENTRY_DELETION_INTERVAL_MILLIS); } ensurePeriodicJobsAreScheduled(); } } case WRITE_DATABASE_CACHE_FULL -> { Loading @@ -390,37 +399,38 @@ public class AppOpHistoryHelper { } mDbHelper.insertAppOpHistory(evictedEvents); } finally { // Just in case initial message is not scheduled. if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_PERIODIC)) { mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_PERIODIC, DB_WRITE_INTERVAL_PERIODIC_MILLIS); } ensurePeriodicJobsAreScheduled(); } } case DELETE_EXPIRED_ENTRIES -> { case DELETE_EXPIRED_ENTRIES_PERIODIC -> { try { long cutOffTimeStamp = System.currentTimeMillis() - mHistoryRetentionMillis; mDbHelper.execSQL( AppOpHistoryTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME, new Object[]{cutOffTimeStamp}); } finally { ensurePeriodicJobsAreScheduled(); } } } } void removeAllPendingMessages() { removeMessages(WRITE_DATABASE_PERIODIC); removeMessages(DELETE_EXPIRED_ENTRIES); removeMessages(DELETE_EXPIRED_ENTRIES_PERIODIC); removeMessages(WRITE_DATABASE_CACHE_FULL); } } /** * A cache for aggregating app op access counts for a time window. Individual app op events * aren't stored on the disk, instead an aggregated event is persisted on the disk. * 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. * * <p> * These events are persisted into sqlite database * 1) Periodic interval. * 2) When the cache become full. * 3) During read call, flush the whole cache to disk. * 3) During read call, flush the cache to disk to make read simpler. * 4) During shutdown. */ class AppOpHistoryCache { Loading @@ -441,7 +451,7 @@ public class AppOpHistoryHelper { * @param rejectCount Reject counts to be aggregated for an event. * @param duration Access duration to be aggregated for an event. */ public void insertOrUpdate(AppOpAccessEvent accessKey, int accessCount, private void insertOrUpdate(AppOpAccessEvent accessKey, int accessCount, int rejectCount, long duration) { synchronized (this) { AggregatedAppOpValues appOpAccessValue = mCache.get(accessKey); Loading Loading @@ -500,7 +510,7 @@ public class AppOpHistoryHelper { /** * Evict specified app ops from cache, and return the list of evicted ops. */ public List<AggregatedAppOpAccessEvent> evict(IntArray ops) { private List<AggregatedAppOpAccessEvent> evict(IntArray ops) { synchronized (this) { List<AggregatedAppOpAccessEvent> cachedOps = new ArrayList<>(); List<AppOpAccessEvent> keysToBeRemoved = new ArrayList<>(); Loading Loading @@ -528,7 +538,7 @@ public class AppOpHistoryHelper { * * @return return all removed entries. */ public List<AggregatedAppOpAccessEvent> evictAll() { private List<AggregatedAppOpAccessEvent> evictAll() { synchronized (this) { List<AggregatedAppOpAccessEvent> cachedOps = snapshot(); mCache.clear(); Loading @@ -539,13 +549,13 @@ public class AppOpHistoryHelper { /** * Remove all entries from the cache. */ public void clear() { private void clear() { synchronized (this) { mCache.clear(); } } public List<AggregatedAppOpAccessEvent> snapshot() { private List<AggregatedAppOpAccessEvent> snapshot() { List<AggregatedAppOpAccessEvent> events = new ArrayList<>(); synchronized (this) { for (Map.Entry<AppOpAccessEvent, AggregatedAppOpValues> event : Loading @@ -557,7 +567,7 @@ public class AppOpHistoryHelper { } /** Remove cached events for given UID and package. */ public void clear(int uid, String packageName) { private void clear(int uid, String packageName) { synchronized (this) { List<AppOpAccessEvent> keysToBeDeleted = new ArrayList<>(); for (Map.Entry<AppOpAccessEvent, AggregatedAppOpValues> event : Loading
services/core/java/com/android/server/appop/AppOpHistoryTable.java +3 −2 Original line number Diff line number Diff line Loading @@ -18,8 +18,9 @@ package com.android.server.appop; /** * SQLite table for storing aggregated app op access events. * Each row summarizes the count of accessed and rejected events that occurred within a specific * time window (e.g., 1 or 15 minutes). This aggregation helps in efficiently storing the app op * Each row summarizes the count of accessed, rejected events, and duration sum that occurred * within a specific time window (e.g., 1 or 15 minutes). This aggregation helps in efficiently * storing the app op * access events. * * <p>The following columns form a composite key to uniquely identify an aggregated record Loading
services/core/java/com/android/server/appop/AppOpsService.java +0 −1 Original line number Diff line number Diff line Loading @@ -6360,7 +6360,6 @@ public class AppOpsService extends IAppOpsService.Stub { pw.println("Wrong parameter: " + args[i]); return; } includeDiscreteOps = true; } else if ("--history".equals(arg)) { dumpHistory = true; } else if (arg.length() > 0 && arg.charAt(0) == '-') { Loading
services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java +92 −53 Original line number Diff line number Diff line Loading @@ -16,66 +16,83 @@ package com.android.server.appop; import android.annotation.NonNull; import android.util.Slog; import java.util.ArrayList; import java.util.List; /** * Helper class for migrating discrete ops from xml to sqlite * Helper class for migrating discrete ops from xml to sqlite or vice versa. */ public class DiscreteOpsMigrationHelper { private static final String LOG_TAG = "DiscreteOpsMigration"; /** * migrate discrete ops from xml to sqlite. */ static void migrateFromXmlToSqlite(DiscreteOpsXmlRegistry sourceRegistry, DiscreteOpsSqlRegistry targetRegistry) { try { DiscreteOpsXmlRegistry.DiscreteOps xmlOps = sourceRegistry.getAllDiscreteOps(); List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps(xmlOps); List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps( xmlOps); targetRegistry.migrateDiscreteAppOpHistory(discreteOps, xmlOps.mChainIdOffset); if (!sourceRegistry.deleteDiscreteOpsDir()) { Slog.w(LOG_TAG, "Couldn't delete appops xml directories."); } } catch (Exception ex) { Slog.e(LOG_TAG, "migrateFromXmlToSqlite failed.", ex); sourceRegistry.deleteDiscreteOpsDir(); } } /** * migrate discrete ops from xml to unified sqlite schema. */ static void migrateFromXmlToUnifiedSchemaSqlite(DiscreteOpsXmlRegistry sourceRegistry, AppOpHistoryHelper targetRegistry) { try { DiscreteOpsXmlRegistry.DiscreteOps xmlOps = sourceRegistry.getAllDiscreteOps(); List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps(xmlOps); List<AggregatedAppOpAccessEvent> convertedOps = new ArrayList<>(); for (DiscreteOpsSqlRegistry.DiscreteOp event: discreteOps) { convertedOps.add(new AggregatedAppOpAccessEvent(event.getUid(), event.getPackageName(), event.getOpCode(), event.getDeviceId(), event.getAttributionTag(), event.getOpFlags(), event.getUidState(), event.getAttributionFlags(), event.getChainId(), event.getAccessTime(), event.getDuration(), 0, 1, 0)); } List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = convertXmlToSqlDiscreteOps( xmlOps); List<AggregatedAppOpAccessEvent> convertedOps = getAggregatedAppOpAccessEvents(discreteOps); targetRegistry.migrateDiscreteAppOpHistory(convertedOps); if (!sourceRegistry.deleteDiscreteOpsDir()) { Slog.w(LOG_TAG, "Couldn't delete appops xml directories."); } } catch (Exception ex) { Slog.e(LOG_TAG, "migrateFromXmlToUnifiedSchemaSqlite failed.", ex); sourceRegistry.deleteDiscreteOpsDir(); } } /** * migrate discrete ops from sqlite to unified-schema sqlite. */ static void migrateFromSqliteToUnifiedSchemaSqlite(DiscreteOpsSqlRegistry sourceRegistry, AppOpHistoryHelper targetRegistry) { try { List<DiscreteOpsSqlRegistry.DiscreteOp> sourceOps = sourceRegistry.getAllDiscreteOps(); List<AggregatedAppOpAccessEvent> convertedOps = new ArrayList<>(); for (DiscreteOpsSqlRegistry.DiscreteOp event: sourceOps) { convertedOps.add(new AggregatedAppOpAccessEvent(event.getUid(), event.getPackageName(), event.getOpCode(), event.getDeviceId(), event.getAttributionTag(), event.getOpFlags(), event.getUidState(), event.getAttributionFlags(), event.getChainId(), event.getAccessTime(), event.getDuration(), 0, 1, 0)); } List<AggregatedAppOpAccessEvent> convertedOps = getAggregatedAppOpAccessEvents(sourceOps); targetRegistry.migrateDiscreteAppOpHistory(convertedOps); if (!sourceRegistry.deleteDatabase()) { Slog.w(LOG_TAG, "Couldn't delete appops sql database."); } } catch (Exception ex) { Slog.e(LOG_TAG, "migrateFromSqliteToUnifiedSchemaSqlite failed.", ex); sourceRegistry.deleteDatabase(); } } /** * rollback discrete ops from unified schema sqlite to sqlite schema. */ static void rollbackFromUnifiedSchemaSqliteToSqlite(AppOpHistoryHelper sourceRegistry, DiscreteOpsSqlRegistry targetRegistry) { try { List<AggregatedAppOpAccessEvent> unifiedSchemaSqliteOps = sourceRegistry.getAppOpHistory(); List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = new ArrayList<>(); Loading @@ -91,8 +108,13 @@ public class DiscreteOpsMigrationHelper { largestChainId = Math.max(largestChainId, event.attributionChainId()); } targetRegistry.migrateDiscreteAppOpHistory(discreteOps, largestChainId); if (!sourceRegistry.deleteDatabase()) { Slog.w(LOG_TAG, "Couldn't delete appops unified sql database."); } } catch (Exception ex) { Slog.e(LOG_TAG, "rollbackFromUnifiedSchemaSqliteToSqlite failed.", ex); sourceRegistry.deleteDatabase(); sourceRegistry.shutdown(); } } /** Loading @@ -100,24 +122,23 @@ public class DiscreteOpsMigrationHelper { */ static void rollbackFromSqliteToXml(DiscreteOpsSqlRegistry sourceRegistry, DiscreteOpsXmlRegistry targetRegistry) { try { List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sourceRegistry.getAllDiscreteOps(); // Only migrate configured discrete ops. Sqlite may contain all runtime ops, and more. List<DiscreteOpsSqlRegistry.DiscreteOp> filteredList = new ArrayList<>(); for (DiscreteOpsSqlRegistry.DiscreteOp opEvent: sqlOps) { if (DiscreteOpsRegistry.isDiscreteOp(opEvent.getOpCode(), opEvent.getOpFlags())) { filteredList.add(opEvent); } } DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(filteredList); DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(sqlOps); targetRegistry.migrateDiscreteAppOpHistory(xmlOps); if (!sourceRegistry.deleteDatabase()) { Slog.w(LOG_TAG, "Couldn't delete appops sql database."); } } catch (Exception ex) { Slog.e(LOG_TAG, "rollbackFromSqliteToXml failed.", ex); sourceRegistry.deleteDatabase(); } } /** * Convert sqlite flat rows to hierarchical data. */ @NonNull private static DiscreteOpsXmlRegistry.DiscreteOps getXmlDiscreteOps( List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps) { DiscreteOpsXmlRegistry.DiscreteOps xmlOps = Loading @@ -140,6 +161,7 @@ public class DiscreteOpsMigrationHelper { /** * Convert xml (hierarchical) data to flat row based data. */ @NonNull private static List<DiscreteOpsSqlRegistry.DiscreteOp> convertXmlToSqlDiscreteOps( DiscreteOpsXmlRegistry.DiscreteOps discreteOps) { List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents = new ArrayList<>(); Loading Loading @@ -173,4 +195,21 @@ public class DiscreteOpsMigrationHelper { return opEvents; } @NonNull private static List<AggregatedAppOpAccessEvent> getAggregatedAppOpAccessEvents( List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps) { List<AggregatedAppOpAccessEvent> convertedOps = new ArrayList<>(); for (DiscreteOpsSqlRegistry.DiscreteOp event : discreteOps) { convertedOps.add( new AggregatedAppOpAccessEvent(event.getUid(), event.getPackageName(), event.getOpCode(), event.getDeviceId(), event.getAttributionTag(), event.getOpFlags(), event.getUidState(), event.getAttributionFlags(), event.getChainId(), event.getAccessTime(), event.getDuration(), 0, 1, 0)); } return convertedOps; } }