Correctly offset app op history on reboot

The offset was computed based off the offset represented by
the most recent historical file while we have to use the time
this file was written.

Also now we persist the history on reboot and shutdown,
significantly minimizing the possibility of data loss.

Added test API to emulate reboot of the history to allow for
precise and tightly controlled test to prevent flakes due to
boot time deviations.

Test: atest android.app.appops.cts.HistoricalAppopsTest#testRebootHistory

Fixes:156853195

Change-Id: I4142371f8bc2b1d710cc8c300e7e79cb03764c04
This commit is contained in:
Svet Ganov
2020-06-04 21:40:26 -07:00
parent 848cfed7df
commit 185ddf644b
5 changed files with 72 additions and 11 deletions

View File

@@ -193,6 +193,7 @@ package android.app {
method public static int opToDefaultMode(@NonNull String);
method public static String opToPermission(int);
method public static int permissionToOpCode(String);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void rebootHistory(long);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void reloadNonHistoricalState();
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void resetHistoryParameters();
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void setHistoryParameters(int, long, int);

View File

@@ -8576,6 +8576,25 @@ public class AppOpsManager {
}
}
/**
* Reboots the ops history.
*
* @param offlineDurationMillis The duration to wait between
* tearing down and initializing the history. Must be greater
* than or equal to zero.
*
* @hide
*/
@TestApi
@RequiresPermission(Manifest.permission.MANAGE_APPOPS)
public void rebootHistory(long offlineDurationMillis) {
try {
mService.rebootHistory(offlineDurationMillis);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Pulls current AppOps access report and picks package and op to watch for next access report
* Returns null if no reports were collected since last call. There is no guarantee of report

View File

@@ -76,6 +76,7 @@ interface IAppOpsService {
void addHistoricalOps(in AppOpsManager.HistoricalOps ops);
void resetHistoryParameters();
void clearHistory();
void rebootHistory(long offlineDurationMillis);
List<AppOpsManager.PackageOps> getUidOps(int uid, in int[] ops);
void setUidMode(int code, int uid, int mode);
@UnsupportedAppUsage

View File

@@ -322,7 +322,7 @@ public class AppOpsService extends IAppOpsService.Stub {
@VisibleForTesting
final SparseArray<UidState> mUidStates = new SparseArray<>();
final HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
long mLastRealtime;
@@ -1978,6 +1978,8 @@ public class AppOpsService extends IAppOpsService.Stub {
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
writeNoteOps();
}
mHistoricalRegistry.shutdown();
}
private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
@@ -5881,6 +5883,25 @@ public class AppOpsService extends IAppOpsService.Stub {
mHistoricalRegistry.clearHistory();
}
@Override
public void rebootHistory(long offlineDurationMillis) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
"rebootHistory");
Preconditions.checkArgument(offlineDurationMillis >= 0);
// Must not hold the appops lock
mHistoricalRegistry.shutdown();
if (offlineDurationMillis > 0) {
SystemClock.sleep(offlineDurationMillis);
}
mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
mHistoricalRegistry.systemReady(mContext.getContentResolver());
mHistoricalRegistry.persistPendingHistory();
}
/**
* Report runtime access to AppOp together with message (including stack trace)
*

View File

@@ -199,6 +199,13 @@ final class HistoricalRegistry {
mInMemoryLock = lock;
}
HistoricalRegistry(@NonNull HistoricalRegistry other) {
this(other.mInMemoryLock);
mMode = other.mMode;
mBaseSnapshotInterval = other.mBaseSnapshotInterval;
mIntervalCompressionMultiplier = other.mIntervalCompressionMultiplier;
}
void systemReady(@NonNull ContentResolver resolver) {
final Uri uri = Settings.Global.getUriFor(Settings.Global.APPOP_HISTORY_PARAMETERS);
resolver.registerContentObserver(uri, false, new ContentObserver(
@@ -223,9 +230,16 @@ final class HistoricalRegistry {
// When starting always adjust history to now.
final long lastPersistTimeMills =
mPersistence.getLastPersistTimeMillisDLocked();
if (lastPersistTimeMills > 0) {
mPendingHistoryOffsetMillis =
System.currentTimeMillis() - lastPersistTimeMills;
mPendingHistoryOffsetMillis = System.currentTimeMillis()
- lastPersistTimeMills;
if (DEBUG) {
Slog.i(LOG_TAG, "Time since last write: "
+ TimeUtils.formatDuration(mPendingHistoryOffsetMillis)
+ " by which to push history on next write");
}
}
}
}
@@ -597,6 +611,9 @@ final class HistoricalRegistry {
return;
}
clearHistoryOnDiskDLocked();
mNextPersistDueTimeMillis = 0;
mPendingHistoryOffsetMillis = 0;
mCurrentHistoricalOps = null;
}
}
}
@@ -650,7 +667,15 @@ final class HistoricalRegistry {
return mCurrentHistoricalOps;
}
private void persistPendingHistory() {
void shutdown() {
synchronized (mInMemoryLock) {
if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) {
persistPendingHistory();
}
}
}
void persistPendingHistory() {
final List<HistoricalOps> pendingWrites;
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
@@ -844,13 +869,7 @@ final class HistoricalRegistry {
if (shortestFile == null) {
return 0;
}
final String shortestNameNoExtension = shortestFile.getName()
.replace(HISTORY_FILE_SUFFIX, "");
try {
return Long.parseLong(shortestNameNoExtension);
} catch (NumberFormatException e) {
return 0;
}
return shortestFile.lastModified();
}
sHistoricalAppOpsDir.finishRead();
} catch (Throwable e) {