1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.appop; 17 18 import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; 19 import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; 20 import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; 21 import static android.app.AppOpsManager.FILTER_BY_UID; 22 import static android.app.AppOpsManager.HISTORY_FLAG_AGGREGATE; 23 import static android.app.AppOpsManager.HISTORY_FLAG_DISCRETE; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.app.AppOpsManager; 28 import android.app.AppOpsManager.HistoricalMode; 29 import android.app.AppOpsManager.HistoricalOp; 30 import android.app.AppOpsManager.HistoricalOps; 31 import android.app.AppOpsManager.HistoricalOpsRequestFilter; 32 import android.app.AppOpsManager.HistoricalPackageOps; 33 import android.app.AppOpsManager.HistoricalUidOps; 34 import android.app.AppOpsManager.OpFlags; 35 import android.app.AppOpsManager.OpHistoryFlags; 36 import android.app.AppOpsManager.UidState; 37 import android.content.ContentResolver; 38 import android.content.Context; 39 import android.database.ContentObserver; 40 import android.net.Uri; 41 import android.os.Build; 42 import android.os.Bundle; 43 import android.os.Debug; 44 import android.os.Environment; 45 import android.os.Message; 46 import android.os.Process; 47 import android.os.RemoteCallback; 48 import android.os.UserHandle; 49 import android.permission.flags.Flags; 50 import android.provider.Settings; 51 import android.util.ArraySet; 52 import android.util.LongSparseArray; 53 import android.util.Slog; 54 import android.util.TimeUtils; 55 import android.util.Xml; 56 57 import com.android.internal.annotations.GuardedBy; 58 import com.android.internal.os.AtomicDirectory; 59 import com.android.internal.util.ArrayUtils; 60 import com.android.internal.util.XmlUtils; 61 import com.android.internal.util.function.pooled.PooledLambda; 62 import com.android.modules.utils.TypedXmlPullParser; 63 import com.android.modules.utils.TypedXmlSerializer; 64 import com.android.server.FgThread; 65 import com.android.server.IoThread; 66 67 import org.xmlpull.v1.XmlPullParserException; 68 69 import java.io.File; 70 import java.io.FileInputStream; 71 import java.io.FileNotFoundException; 72 import java.io.FileOutputStream; 73 import java.io.IOException; 74 import java.io.PrintWriter; 75 import java.nio.file.Files; 76 import java.text.SimpleDateFormat; 77 import java.util.ArrayList; 78 import java.util.Arrays; 79 import java.util.Collections; 80 import java.util.Date; 81 import java.util.LinkedList; 82 import java.util.List; 83 import java.util.Objects; 84 import java.util.Set; 85 import java.util.concurrent.TimeUnit; 86 87 /** 88 * This class manages historical app op state. This includes reading, persistence, 89 * accounting, querying. 90 * <p> 91 * The history is kept forever in multiple files. Each file time contains the 92 * relative offset from the current time which time is encoded in the file name. 93 * The files contain historical app op state snapshots which have times that 94 * are relative to the time of the container file. 95 * 96 * The data in the files are stored in a logarithmic fashion where where every 97 * subsequent file would contain data for ten times longer interval with ten 98 * times more time distance between snapshots. Hence, the more time passes 99 * the lesser the fidelity. 100 * <p> 101 * For example, the first file would contain data for 1 days with snapshots 102 * every 0.1 days, the next file would contain data for the period 1 to 10 103 * days with snapshots every 1 days, and so on. 104 * <p> 105 * THREADING AND LOCKING: Reported ops must be processed as quickly as possible. 106 * We keep ops pending to be persisted in memory and write to disk on a background 107 * thread. Hence, methods that report op changes are locking only the in memory 108 * state guarded by the mInMemoryLock which happens to be the app ops service lock 109 * avoiding a lock addition on the critical path. When a query comes we need to 110 * evaluate it based off both in memory and on disk state. This means they need to 111 * be frozen with respect to each other and not change from the querying caller's 112 * perspective. To achieve this we add a dedicated mOnDiskLock to guard the on 113 * disk state. To have fast critical path we need to limit the locking of the 114 * mInMemoryLock, thus for operations that touch in memory and on disk state one 115 * must grab first the mOnDiskLock and then the mInMemoryLock and limit the 116 * in memory lock to extraction of relevant data. Locking order is critical to 117 * avoid deadlocks. The convention is that xxxDLocked suffix means the method 118 * must be called with the mOnDiskLock lock, xxxMLocked suffix means the method 119 * must be called with the mInMemoryLock, xxxDMLocked suffix means the method 120 * must be called with the mOnDiskLock and mInMemoryLock locks acquired in that 121 * exact order. 122 * <p> 123 * INITIALIZATION: We can initialize persistence only after the system is ready 124 * as we need to check the optional configuration override from the settings 125 * database which is not initialized at the time the app ops service is created. 126 * This means that all entry points that touch persistence should be short 127 * circuited via isPersistenceInitialized() check. 128 */ 129 // TODO (bug:122218838): Make sure we handle start of epoch time 130 // TODO (bug:122218838): Validate changed time is handled correctly 131 final class LegacyHistoricalRegistry implements HistoricalRegistryInterface { 132 private static final boolean DEBUG = false; 133 private static final boolean KEEP_WTF_LOG = Build.IS_DEBUGGABLE; 134 135 private static final String LOG_TAG = LegacyHistoricalRegistry.class.getSimpleName(); 136 137 private static final String PARAMETER_DELIMITER = ","; 138 private static final String PARAMETER_ASSIGNMENT = "="; 139 140 private volatile @NonNull DiscreteOpsRegistry mDiscreteRegistry; 141 142 @GuardedBy("mLock") 143 private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>(); 144 145 // Lock for read/write access to on disk state 146 private final Object mOnDiskLock = new Object(); 147 148 //Lock for read/write access to in memory state 149 private final @NonNull Object mInMemoryLock; 150 151 private static final int MSG_WRITE_PENDING_HISTORY = 1; 152 153 // See mMode 154 private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE; 155 156 // See mBaseSnapshotInterval 157 private static final long DEFAULT_SNAPSHOT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15); 158 159 // See mIntervalCompressionMultiplier 160 private static final long DEFAULT_COMPRESSION_STEP = 10; 161 162 private static final String HISTORY_FILE_SUFFIX = ".xml"; 163 164 /** 165 * Whether history is enabled. 166 */ 167 @GuardedBy("mInMemoryLock") 168 private int mMode = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE; 169 170 /** 171 * This granularity has been chosen to allow clean delineation for intervals 172 * humans understand, 15 min, 60, min, a day, a week, a month (30 days). 173 */ 174 @GuardedBy("mInMemoryLock") 175 private long mBaseSnapshotInterval = DEFAULT_SNAPSHOT_INTERVAL_MILLIS; 176 177 /** 178 * The compression between steps. Each subsequent step is this much longer 179 * in terms of duration and each snapshot is this much more apart from the 180 * previous step. 181 */ 182 @GuardedBy("mInMemoryLock") 183 private long mIntervalCompressionMultiplier = DEFAULT_COMPRESSION_STEP; 184 185 // The current ops to which to add statistics. 186 @GuardedBy("mInMemoryLock") 187 private @Nullable HistoricalOps mCurrentHistoricalOps; 188 189 // The time we should write the next snapshot. 190 @GuardedBy("mInMemoryLock") 191 private long mNextPersistDueTimeMillis; 192 193 // How much to offset the history on the next write. 194 @GuardedBy("mInMemoryLock") 195 private long mPendingHistoryOffsetMillis; 196 197 // Object managing persistence (read/write) 198 @GuardedBy("mOnDiskLock") 199 private Persistence mPersistence; 200 201 private final Context mContext; 202 LegacyHistoricalRegistry(@onNull Object lock, Context context)203 LegacyHistoricalRegistry(@NonNull Object lock, Context context) { 204 mInMemoryLock = lock; 205 mContext = context; 206 if (Flags.enableSqliteAppopsAccesses()) { 207 mDiscreteRegistry = new DiscreteOpsSqlRegistry(context); 208 } else { 209 mDiscreteRegistry = new DiscreteOpsXmlRegistry(lock); 210 } 211 } 212 LegacyHistoricalRegistry(@onNull LegacyHistoricalRegistry other)213 LegacyHistoricalRegistry(@NonNull LegacyHistoricalRegistry other) { 214 this(other.mInMemoryLock, other.mContext); 215 mMode = other.mMode; 216 mBaseSnapshotInterval = other.mBaseSnapshotInterval; 217 mIntervalCompressionMultiplier = other.mIntervalCompressionMultiplier; 218 mDiscreteRegistry = other.mDiscreteRegistry; 219 } 220 221 @Override systemReady(@onNull ContentResolver resolver)222 public void systemReady(@NonNull ContentResolver resolver) { 223 mDiscreteRegistry.systemReady(); 224 final Uri uri = Settings.Global.getUriFor(Settings.Global.APPOP_HISTORY_PARAMETERS); 225 resolver.registerContentObserver(uri, false, new ContentObserver( 226 FgThread.getHandler()) { 227 @Override 228 public void onChange(boolean selfChange) { 229 updateParametersFromSetting(resolver); 230 } 231 }); 232 233 updateParametersFromSetting(resolver); 234 235 synchronized (mOnDiskLock) { 236 synchronized (mInMemoryLock) { 237 if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) { 238 // Can be uninitialized if there is no config in the settings table. 239 if (!isPersistenceInitializedMLocked()) { 240 mPersistence = new Persistence(mBaseSnapshotInterval, 241 mIntervalCompressionMultiplier); 242 } 243 244 // When starting always adjust history to now. 245 final long lastPersistTimeMills = 246 mPersistence.getLastPersistTimeMillisDLocked(); 247 248 if (lastPersistTimeMills > 0) { 249 mPendingHistoryOffsetMillis = System.currentTimeMillis() 250 - lastPersistTimeMills; 251 252 if (DEBUG) { 253 Slog.i(LOG_TAG, "Time since last write: " 254 + TimeUtils.formatDuration(mPendingHistoryOffsetMillis) 255 + " by which to push history on next write"); 256 } 257 } 258 } 259 } 260 } 261 if (Flags.enableSqliteAppopsAccesses()) { 262 if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) { 263 DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry; 264 DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mContext); 265 DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry); 266 } 267 } else { 268 if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite 269 DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext); 270 DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry; 271 DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry); 272 } 273 } 274 } 275 isPersistenceInitializedMLocked()276 private boolean isPersistenceInitializedMLocked() { 277 return mPersistence != null; 278 } 279 updateParametersFromSetting(@onNull ContentResolver resolver)280 private void updateParametersFromSetting(@NonNull ContentResolver resolver) { 281 final String setting = Settings.Global.getString(resolver, 282 Settings.Global.APPOP_HISTORY_PARAMETERS); 283 if (setting == null) { 284 return; 285 } 286 String modeValue = null; 287 String baseSnapshotIntervalValue = null; 288 String intervalMultiplierValue = null; 289 final String[] parameters = setting.split(PARAMETER_DELIMITER); 290 for (String parameter : parameters) { 291 final String[] parts = parameter.split(PARAMETER_ASSIGNMENT); 292 if (parts.length == 2) { 293 final String key = parts[0].trim(); 294 switch (key) { 295 case Settings.Global.APPOP_HISTORY_MODE: { 296 modeValue = parts[1].trim(); 297 } break; 298 case Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS: { 299 baseSnapshotIntervalValue = parts[1].trim(); 300 } break; 301 case Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER: { 302 intervalMultiplierValue = parts[1].trim(); 303 } break; 304 default: { 305 Slog.w(LOG_TAG, "Unknown parameter: " + parameter); 306 } 307 } 308 } 309 } 310 if (modeValue != null && baseSnapshotIntervalValue != null 311 && intervalMultiplierValue != null) { 312 try { 313 final int mode = AppOpsManager.parseHistoricalMode(modeValue); 314 final long baseSnapshotInterval = Long.parseLong(baseSnapshotIntervalValue); 315 final int intervalCompressionMultiplier = Integer.parseInt(intervalMultiplierValue); 316 setHistoryParameters(mode, baseSnapshotInterval, intervalCompressionMultiplier); 317 return; 318 } catch (NumberFormatException ignored) { } 319 } 320 Slog.w(LOG_TAG, "Bad value for" + Settings.Global.APPOP_HISTORY_PARAMETERS 321 + "=" + setting + " resetting!"); 322 } 323 324 325 @Override dump(String prefix, PrintWriter pw, int filterUid, @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp, @HistoricalOpsRequestFilter int filter)326 public void dump(String prefix, PrintWriter pw, int filterUid, 327 @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp, 328 @HistoricalOpsRequestFilter int filter) { 329 synchronized (mOnDiskLock) { 330 synchronized (mInMemoryLock) { 331 pw.println(); 332 pw.print(prefix); 333 pw.print("History:"); 334 335 pw.print(" mode="); 336 pw.println(AppOpsManager.historicalModeToString(mMode)); 337 338 final StringDumpVisitor visitor = new StringDumpVisitor(prefix + " ", 339 pw, filterUid, filterPackage, filterAttributionTag, filterOp, filter); 340 final long nowMillis = System.currentTimeMillis(); 341 342 // Dump in memory state first 343 final HistoricalOps currentOps = getUpdatedPendingHistoricalOpsMLocked( 344 nowMillis); 345 makeRelativeToEpochStart(currentOps, nowMillis); 346 currentOps.accept(visitor); 347 348 if (!isPersistenceInitializedMLocked()) { 349 Slog.e(LOG_TAG, "Interaction before persistence initialized"); 350 return; 351 } 352 353 final List<HistoricalOps> ops = mPersistence.readHistoryDLocked(); 354 if (ops != null) { 355 // TODO (bug:122218838): Make sure this is properly dumped 356 final long remainingToFillBatchMillis = mNextPersistDueTimeMillis 357 - nowMillis - mBaseSnapshotInterval; 358 final int opCount = ops.size(); 359 for (int i = 0; i < opCount; i++) { 360 final HistoricalOps op = ops.get(i); 361 op.offsetBeginAndEndTime(remainingToFillBatchMillis); 362 makeRelativeToEpochStart(op, nowMillis); 363 op.accept(visitor); 364 } 365 } else { 366 pw.println(" Empty"); 367 } 368 } 369 } 370 } 371 372 @Override dumpDiscreteData(@onNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, @Nullable String attributionTagFilter, @HistoricalOpsRequestFilter int filter, int dumpOp, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)373 public void dumpDiscreteData(@NonNull PrintWriter pw, int uidFilter, 374 @Nullable String packageNameFilter, @Nullable String attributionTagFilter, 375 @HistoricalOpsRequestFilter int filter, int dumpOp, 376 @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, 377 int nDiscreteOps) { 378 mDiscreteRegistry.dump(pw, uidFilter, packageNameFilter, attributionTagFilter, filter, 379 dumpOp, sdf, date, prefix, nDiscreteOps); 380 } 381 getMode()382 @HistoricalMode int getMode() { 383 synchronized (mInMemoryLock) { 384 return mMode; 385 } 386 } 387 388 @Override getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String[] opNames, @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis, @OpFlags int flags, String[] attributionExemptedPackages, @NonNull RemoteCallback callback)389 public void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName, 390 @Nullable String attributionTag, @Nullable String[] opNames, 391 @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter, 392 long beginTimeMillis, long endTimeMillis, @OpFlags int flags, 393 String[] attributionExemptedPackages, @NonNull RemoteCallback callback) { 394 final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis); 395 396 if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) { 397 synchronized (mOnDiskLock) { 398 synchronized (mInMemoryLock) { 399 if (!isPersistenceInitializedMLocked()) { 400 Slog.e(LOG_TAG, "Interaction before persistence initialized"); 401 callback.sendResult(new Bundle()); 402 return; 403 } 404 } 405 mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, 406 attributionTag, 407 opNames, filter, beginTimeMillis, endTimeMillis, flags); 408 } 409 } 410 411 if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) { 412 mDiscreteRegistry.addFilteredDiscreteOpsToHistoricalOps(result, beginTimeMillis, 413 endTimeMillis, filter, uid, packageName, opNames, attributionTag, 414 flags, new ArraySet<>(attributionExemptedPackages)); 415 } 416 417 final Bundle payload = new Bundle(); 418 payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); 419 callback.sendResult(payload); 420 } 421 422 @Override getHistoricalOps(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String[] opNames, @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis, @OpFlags int flags, @Nullable String[] attributionExemptPkgs, @NonNull RemoteCallback callback)423 public void getHistoricalOps(int uid, @Nullable String packageName, 424 @Nullable String attributionTag, @Nullable String[] opNames, 425 @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter, 426 long beginTimeMillis, long endTimeMillis, @OpFlags int flags, 427 @Nullable String[] attributionExemptPkgs, @NonNull RemoteCallback callback) { 428 final long currentTimeMillis = System.currentTimeMillis(); 429 if (endTimeMillis == Long.MAX_VALUE) { 430 endTimeMillis = currentTimeMillis; 431 } 432 433 final Bundle payload = new Bundle(); 434 435 // Argument times are based off epoch start while our internal store is 436 // based off now, so take this into account. 437 final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0); 438 final long inMemoryAdjEndTimeMillis = Math.max(currentTimeMillis - beginTimeMillis, 0); 439 final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis, 440 inMemoryAdjEndTimeMillis); 441 442 if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) { 443 mDiscreteRegistry.addFilteredDiscreteOpsToHistoricalOps(result, beginTimeMillis, 444 endTimeMillis, filter, uid, packageName, opNames, attributionTag, flags, 445 new ArraySet<>(attributionExemptPkgs)); 446 } 447 448 if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) { 449 synchronized (mOnDiskLock) { 450 final List<HistoricalOps> pendingWrites; 451 final HistoricalOps currentOps; 452 boolean collectOpsFromDisk; 453 454 synchronized (mInMemoryLock) { 455 if (!isPersistenceInitializedMLocked()) { 456 Slog.e(LOG_TAG, "Interaction before persistence initialized"); 457 callback.sendResult(new Bundle()); 458 return; 459 } 460 461 currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis); 462 if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis() 463 || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) { 464 // Some of the current batch falls into the query, so extract that. 465 final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps); 466 currentOpsCopy.filter(uid, packageName, attributionTag, opNames, 467 historyFlags, filter, inMemoryAdjBeginTimeMillis, 468 inMemoryAdjEndTimeMillis); 469 result.merge(currentOpsCopy); 470 } 471 pendingWrites = new ArrayList<>(mPendingWrites); 472 mPendingWrites.clear(); 473 collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis(); 474 } 475 476 // If the query was only for in-memory state - done. 477 if (collectOpsFromDisk) { 478 // If there is a write in flight we need to force it now 479 persistPendingHistory(pendingWrites); 480 // Collect persisted state. 481 final long onDiskAndInMemoryOffsetMillis = currentTimeMillis 482 - mNextPersistDueTimeMillis + mBaseSnapshotInterval; 483 final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis 484 - onDiskAndInMemoryOffsetMillis, 0); 485 final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis 486 - onDiskAndInMemoryOffsetMillis, 0); 487 mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, 488 attributionTag, 489 opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, 490 flags); 491 } 492 } 493 } 494 // Rebase the result time to be since epoch. 495 result.setBeginAndEndTime(beginTimeMillis, endTimeMillis); 496 497 // Send back the result. 498 payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); 499 callback.sendResult(payload); 500 } 501 502 @Override 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, int accessCount)503 public void incrementOpAccessedCount(int op, int uid, @NonNull String packageName, 504 @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, 505 @OpFlags int flags, long accessTime, 506 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, 507 int accessCount) { 508 synchronized (mInMemoryLock) { 509 if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { 510 if (!isPersistenceInitializedMLocked()) { 511 Slog.v(LOG_TAG, "Interaction before persistence initialized"); 512 return; 513 } 514 getUpdatedPendingHistoricalOpsMLocked( 515 System.currentTimeMillis()).increaseAccessCount(op, uid, packageName, 516 attributionTag, uidState, flags, accessCount); 517 518 mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, 519 attributionTag, flags, uidState, accessTime, -1, attributionFlags, 520 attributionChainId); 521 } 522 } 523 } 524 525 @Override incrementOpRejectedCount(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags)526 public void incrementOpRejectedCount(int op, int uid, @NonNull String packageName, 527 @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags) { 528 synchronized (mInMemoryLock) { 529 if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { 530 if (!isPersistenceInitializedMLocked()) { 531 Slog.v(LOG_TAG, "Interaction before persistence initialized"); 532 return; 533 } 534 getUpdatedPendingHistoricalOpsMLocked( 535 System.currentTimeMillis()).increaseRejectCount(op, uid, packageName, 536 attributionTag, uidState, flags, 1); 537 } 538 } 539 } 540 541 @Override 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)542 public void increaseOpAccessDuration(int op, int uid, @NonNull String packageName, 543 @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, 544 @OpFlags int flags, long eventStartTime, long increment, 545 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { 546 synchronized (mInMemoryLock) { 547 if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { 548 if (!isPersistenceInitializedMLocked()) { 549 Slog.v(LOG_TAG, "Interaction before persistence initialized"); 550 return; 551 } 552 getUpdatedPendingHistoricalOpsMLocked( 553 System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName, 554 attributionTag, uidState, flags, increment); 555 mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, 556 attributionTag, flags, uidState, eventStartTime, increment, 557 attributionFlags, attributionChainId); 558 } 559 } 560 } 561 562 @Override setHistoryParameters(@istoricalMode int mode, long baseSnapshotInterval, long intervalCompressionMultiplier)563 public void setHistoryParameters(@HistoricalMode int mode, 564 long baseSnapshotInterval, long intervalCompressionMultiplier) { 565 synchronized (mOnDiskLock) { 566 synchronized (mInMemoryLock) { 567 // NOTE: We allow this call if persistence is not initialized as 568 // it is a part of the persistence initialization process. 569 boolean resampleHistory = false; 570 Slog.i(LOG_TAG, "New history parameters: mode:" 571 + AppOpsManager.historicalModeToString(mode) + " baseSnapshotInterval:" 572 + baseSnapshotInterval + " intervalCompressionMultiplier:" 573 + intervalCompressionMultiplier); 574 if (mMode != mode) { 575 mMode = mode; 576 if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) { 577 clearHistoryOnDiskDLocked(); 578 } 579 if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE) { 580 mDiscreteRegistry.setDebugMode(true); 581 } 582 } 583 if (mBaseSnapshotInterval != baseSnapshotInterval) { 584 mBaseSnapshotInterval = baseSnapshotInterval; 585 resampleHistory = true; 586 } 587 if (mIntervalCompressionMultiplier != intervalCompressionMultiplier) { 588 mIntervalCompressionMultiplier = intervalCompressionMultiplier; 589 resampleHistory = true; 590 } 591 if (resampleHistory) { 592 resampleHistoryOnDiskInMemoryDMLocked(0); 593 } 594 } 595 } 596 } 597 598 @Override offsetHistory(long offsetMillis)599 public void offsetHistory(long offsetMillis) { 600 synchronized (mOnDiskLock) { 601 synchronized (mInMemoryLock) { 602 if (!isPersistenceInitializedMLocked()) { 603 Slog.e(LOG_TAG, "Interaction before persistence initialized"); 604 return; 605 } 606 } 607 final List<HistoricalOps> history = mPersistence.readHistoryDLocked(); 608 clearHistoricalRegistry(); 609 if (history != null) { 610 final int historySize = history.size(); 611 for (int i = 0; i < historySize; i++) { 612 final HistoricalOps ops = history.get(i); 613 ops.offsetBeginAndEndTime(offsetMillis); 614 } 615 if (offsetMillis < 0) { 616 pruneFutureOps(history); 617 } 618 mPersistence.persistHistoricalOpsDLocked(history); 619 } 620 } 621 mDiscreteRegistry.offsetHistory(offsetMillis); 622 } 623 624 @Override addHistoricalOps(HistoricalOps ops)625 public void addHistoricalOps(HistoricalOps ops) { 626 final List<HistoricalOps> pendingWrites; 627 synchronized (mInMemoryLock) { 628 if (!isPersistenceInitializedMLocked()) { 629 Slog.d(LOG_TAG, "Interaction before persistence initialized"); 630 return; 631 } 632 // The history files start from mBaseSnapshotInterval - take this into account. 633 ops.offsetBeginAndEndTime(mBaseSnapshotInterval); 634 mPendingWrites.offerFirst(ops); 635 pendingWrites = new ArrayList<>(mPendingWrites); 636 mPendingWrites.clear(); 637 } 638 persistPendingHistory(pendingWrites); 639 } 640 resampleHistoryOnDiskInMemoryDMLocked(long offsetMillis)641 private void resampleHistoryOnDiskInMemoryDMLocked(long offsetMillis) { 642 mPersistence = new Persistence(mBaseSnapshotInterval, mIntervalCompressionMultiplier); 643 offsetHistory(offsetMillis); 644 } 645 646 @Override resetHistoryParameters()647 public void resetHistoryParameters() { 648 if (!isPersistenceInitializedMLocked()) { 649 Slog.d(LOG_TAG, "Interaction before persistence initialized"); 650 return; 651 } 652 setHistoryParameters(DEFAULT_MODE, DEFAULT_SNAPSHOT_INTERVAL_MILLIS, 653 DEFAULT_COMPRESSION_STEP); 654 mDiscreteRegistry.setDebugMode(false); 655 } 656 657 @Override clearHistory(int uid, String packageName)658 public void clearHistory(int uid, String packageName) { 659 synchronized (mOnDiskLock) { 660 synchronized (mInMemoryLock) { 661 if (!isPersistenceInitializedMLocked()) { 662 Slog.d(LOG_TAG, "Interaction before persistence initialized"); 663 return; 664 } 665 if (mMode != AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { 666 return; 667 } 668 669 for (int index = 0; index < mPendingWrites.size(); index++) { 670 mPendingWrites.get(index).clearHistory(uid, packageName); 671 } 672 673 getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis()) 674 .clearHistory(uid, packageName); 675 676 mPersistence.clearHistoryDLocked(uid, packageName); 677 } 678 } 679 mDiscreteRegistry.clearHistory(uid, packageName); 680 } 681 682 @Override writeAndClearDiscreteHistory()683 public void writeAndClearDiscreteHistory() { 684 mDiscreteRegistry.writeAndClearOldAccessHistory(); 685 } 686 687 @Override clearAllHistory()688 public void clearAllHistory() { 689 clearHistoricalRegistry(); 690 mDiscreteRegistry.clearHistory(); 691 } 692 clearHistoricalRegistry()693 void clearHistoricalRegistry() { 694 synchronized (mOnDiskLock) { 695 synchronized (mInMemoryLock) { 696 if (!isPersistenceInitializedMLocked()) { 697 Slog.d(LOG_TAG, "Interaction before persistence initialized"); 698 return; 699 } 700 clearHistoryOnDiskDLocked(); 701 mNextPersistDueTimeMillis = 0; 702 mPendingHistoryOffsetMillis = 0; 703 mCurrentHistoricalOps = null; 704 } 705 } 706 } 707 clearHistoryOnDiskDLocked()708 private void clearHistoryOnDiskDLocked() { 709 IoThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY); 710 synchronized (mInMemoryLock) { 711 mCurrentHistoricalOps = null; 712 mNextPersistDueTimeMillis = System.currentTimeMillis(); 713 mPendingWrites.clear(); 714 } 715 Persistence.clearHistoryDLocked(); 716 } 717 getUpdatedPendingHistoricalOpsMLocked(long now)718 private @NonNull HistoricalOps getUpdatedPendingHistoricalOpsMLocked(long now) { 719 if (mCurrentHistoricalOps != null) { 720 final long remainingTimeMillis = mNextPersistDueTimeMillis - now; 721 if (remainingTimeMillis > mBaseSnapshotInterval) { 722 // If time went backwards we need to push history to the future with the 723 // overflow over our snapshot interval. If time went forward do nothing 724 // as we would naturally push history into the past on the next write. 725 mPendingHistoryOffsetMillis = remainingTimeMillis - mBaseSnapshotInterval; 726 } 727 final long elapsedTimeMillis = mBaseSnapshotInterval - remainingTimeMillis; 728 mCurrentHistoricalOps.setEndTime(elapsedTimeMillis); 729 if (remainingTimeMillis > 0) { 730 if (DEBUG) { 731 Slog.i(LOG_TAG, "Returning current in-memory state"); 732 } 733 return mCurrentHistoricalOps; 734 } 735 if (mCurrentHistoricalOps.isEmpty()) { 736 mCurrentHistoricalOps.setBeginAndEndTime(0, 0); 737 mNextPersistDueTimeMillis = now + mBaseSnapshotInterval; 738 return mCurrentHistoricalOps; 739 } 740 // The current batch is full, so persist taking into account overdue persist time. 741 mCurrentHistoricalOps.offsetBeginAndEndTime(mBaseSnapshotInterval); 742 mCurrentHistoricalOps.setBeginTime(mCurrentHistoricalOps.getEndTimeMillis() 743 - mBaseSnapshotInterval); 744 final long overdueTimeMillis = Math.abs(remainingTimeMillis); 745 mCurrentHistoricalOps.offsetBeginAndEndTime(overdueTimeMillis); 746 schedulePersistHistoricalOpsMLocked(mCurrentHistoricalOps); 747 } 748 // The current batch is in the future, i.e. not complete yet. 749 mCurrentHistoricalOps = new HistoricalOps(0, 0); 750 mNextPersistDueTimeMillis = now + mBaseSnapshotInterval; 751 if (DEBUG) { 752 Slog.i(LOG_TAG, "Returning new in-memory state"); 753 } 754 return mCurrentHistoricalOps; 755 } 756 757 @Override shutdown()758 public void shutdown() { 759 synchronized (mInMemoryLock) { 760 if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) { 761 return; 762 } 763 } 764 // Do not call persistPendingHistory inside the memory lock, due to possible deadlock 765 persistPendingHistory(); 766 mDiscreteRegistry.shutdown(); 767 } 768 769 @Override persistPendingHistory()770 public void persistPendingHistory() { 771 final List<HistoricalOps> pendingWrites; 772 synchronized (mOnDiskLock) { 773 synchronized (mInMemoryLock) { 774 pendingWrites = new ArrayList<>(mPendingWrites); 775 mPendingWrites.clear(); 776 if (mPendingHistoryOffsetMillis != 0) { 777 resampleHistoryOnDiskInMemoryDMLocked(mPendingHistoryOffsetMillis); 778 mPendingHistoryOffsetMillis = 0; 779 } 780 } 781 persistPendingHistory(pendingWrites); 782 } 783 mDiscreteRegistry.writeAndClearOldAccessHistory(); 784 } 785 persistPendingHistory(@onNull List<HistoricalOps> pendingWrites)786 private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) { 787 synchronized (mOnDiskLock) { 788 IoThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY); 789 if (pendingWrites.isEmpty()) { 790 return; 791 } 792 final int opCount = pendingWrites.size(); 793 // Pending writes are offset relative to each other, so take this 794 // into account to persist everything in one shot - single write. 795 for (int i = 0; i < opCount; i++) { 796 final HistoricalOps current = pendingWrites.get(i); 797 if (i > 0) { 798 final HistoricalOps previous = pendingWrites.get(i - 1); 799 current.offsetBeginAndEndTime(previous.getBeginTimeMillis()); 800 } 801 } 802 mPersistence.persistHistoricalOpsDLocked(pendingWrites); 803 } 804 } 805 schedulePersistHistoricalOpsMLocked(@onNull HistoricalOps ops)806 private void schedulePersistHistoricalOpsMLocked(@NonNull HistoricalOps ops) { 807 final Message message = PooledLambda.obtainMessage( 808 LegacyHistoricalRegistry::persistPendingHistory, LegacyHistoricalRegistry.this); 809 message.what = MSG_WRITE_PENDING_HISTORY; 810 IoThread.getHandler().sendMessage(message); 811 mPendingWrites.offerFirst(ops); 812 } 813 makeRelativeToEpochStart(@onNull HistoricalOps ops, long nowMillis)814 private static void makeRelativeToEpochStart(@NonNull HistoricalOps ops, long nowMillis) { 815 ops.setBeginAndEndTime(nowMillis - ops.getEndTimeMillis(), 816 nowMillis - ops.getBeginTimeMillis()); 817 } 818 pruneFutureOps(@onNull List<HistoricalOps> ops)819 private void pruneFutureOps(@NonNull List<HistoricalOps> ops) { 820 final int opCount = ops.size(); 821 for (int i = opCount - 1; i >= 0; i--) { 822 final HistoricalOps op = ops.get(i); 823 if (op.getEndTimeMillis() <= mBaseSnapshotInterval) { 824 ops.remove(i); 825 } else if (op.getBeginTimeMillis() < mBaseSnapshotInterval) { 826 final double filterScale = (double) (op.getEndTimeMillis() - mBaseSnapshotInterval) 827 / (double) op.getDurationMillis(); 828 Persistence.spliceFromBeginning(op, filterScale); 829 } 830 } 831 } 832 833 private static final class Persistence { 834 private static final boolean DEBUG = false; 835 836 private static final String LOG_TAG = Persistence.class.getSimpleName(); 837 838 private static final String TAG_HISTORY = "history"; 839 private static final String TAG_OPS = "ops"; 840 private static final String TAG_UID = "uid"; 841 private static final String TAG_PACKAGE = "pkg"; 842 private static final String TAG_ATTRIBUTION = "ftr"; 843 private static final String TAG_OP = "op"; 844 private static final String TAG_STATE = "st"; 845 846 private static final String ATTR_VERSION = "ver"; 847 private static final String ATTR_NAME = "na"; 848 private static final String ATTR_ACCESS_COUNT = "ac"; 849 private static final String ATTR_REJECT_COUNT = "rc"; 850 private static final String ATTR_ACCESS_DURATION = "du"; 851 private static final String ATTR_BEGIN_TIME = "beg"; 852 private static final String ATTR_END_TIME = "end"; 853 private static final String ATTR_OVERFLOW = "ov"; 854 855 private static final int CURRENT_VERSION = 2; 856 857 private final long mBaseSnapshotInterval; 858 private final long mIntervalCompressionMultiplier; 859 Persistence(long baseSnapshotInterval, long intervalCompressionMultiplier)860 Persistence(long baseSnapshotInterval, long intervalCompressionMultiplier) { 861 mBaseSnapshotInterval = baseSnapshotInterval; 862 mIntervalCompressionMultiplier = intervalCompressionMultiplier; 863 } 864 865 private static final AtomicDirectory sHistoricalAppOpsDir = new AtomicDirectory( 866 new File(new File(Environment.getDataSystemDirectory(), "appops"), "history")); 867 generateFile(@onNull File baseDir, int depth)868 private File generateFile(@NonNull File baseDir, int depth) { 869 final long globalBeginMillis = computeGlobalIntervalBeginMillis(depth); 870 return new File(baseDir, Long.toString(globalBeginMillis) + HISTORY_FILE_SUFFIX); 871 } 872 clearHistoryDLocked(int uid, String packageName)873 void clearHistoryDLocked(int uid, String packageName) { 874 List<HistoricalOps> historicalOps = readHistoryDLocked(); 875 876 if (historicalOps == null) { 877 return; 878 } 879 880 for (int index = 0; index < historicalOps.size(); index++) { 881 historicalOps.get(index).clearHistory(uid, packageName); 882 } 883 884 clearHistoryDLocked(); 885 886 persistHistoricalOpsDLocked(historicalOps); 887 } 888 clearHistoryDLocked()889 static void clearHistoryDLocked() { 890 sHistoricalAppOpsDir.delete(); 891 } 892 persistHistoricalOpsDLocked(@onNull List<HistoricalOps> ops)893 void persistHistoricalOpsDLocked(@NonNull List<HistoricalOps> ops) { 894 if (DEBUG) { 895 Slog.i(LOG_TAG, "Persisting ops:\n" + opsToDebugString(ops)); 896 enforceOpsWellFormed(ops); 897 } 898 try { 899 final File newBaseDir = sHistoricalAppOpsDir.startWrite(); 900 final File oldBaseDir = sHistoricalAppOpsDir.getBackupDirectory(); 901 final HistoricalFilesInvariant filesInvariant; 902 if (DEBUG) { 903 filesInvariant = new HistoricalFilesInvariant(); 904 filesInvariant.startTracking(oldBaseDir); 905 } 906 final Set<String> oldFileNames = getHistoricalFileNames(oldBaseDir); 907 handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops, 908 oldFileNames, 0); 909 if (DEBUG) { 910 filesInvariant.stopTracking(newBaseDir); 911 } 912 sHistoricalAppOpsDir.finishWrite(); 913 } catch (Throwable t) { 914 wtf("Failed to write historical app ops, restoring backup", t, null); 915 sHistoricalAppOpsDir.failWrite(); 916 } 917 } 918 readHistoryRawDLocked()919 @Nullable List<HistoricalOps> readHistoryRawDLocked() { 920 return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/, 921 null /*filterPackageName*/, null /*filterAttributionTag*/, 922 null /*filterOpNames*/, 0 /*filter*/, 0 /*filterBeginTimeMills*/, 923 Long.MAX_VALUE /*filterEndTimeMills*/, AppOpsManager.OP_FLAGS_ALL); 924 } 925 readHistoryDLocked()926 @Nullable List<HistoricalOps> readHistoryDLocked() { 927 final List<HistoricalOps> result = readHistoryRawDLocked(); 928 // Take into account in memory state duration. 929 if (result != null) { 930 final int opCount = result.size(); 931 for (int i = 0; i < opCount; i++) { 932 result.get(i).offsetBeginAndEndTime(mBaseSnapshotInterval); 933 } 934 } 935 return result; 936 } 937 getLastPersistTimeMillisDLocked()938 long getLastPersistTimeMillisDLocked() { 939 File baseDir = null; 940 try { 941 baseDir = sHistoricalAppOpsDir.startRead(); 942 final File[] files = baseDir.listFiles(); 943 if (files != null && files.length > 0) { 944 File shortestFile = null; 945 for (File candidate : files) { 946 final String candidateName = candidate.getName(); 947 if (!candidateName.endsWith(HISTORY_FILE_SUFFIX)) { 948 continue; 949 } 950 if (shortestFile == null) { 951 shortestFile = candidate; 952 } else if (candidateName.length() < shortestFile.getName().length()) { 953 shortestFile = candidate; 954 } 955 } 956 if (shortestFile == null) { 957 return 0; 958 } 959 return shortestFile.lastModified(); 960 } 961 sHistoricalAppOpsDir.finishRead(); 962 } catch (Throwable e) { 963 wtf("Error reading historical app ops. Deleting history.", e, baseDir); 964 sHistoricalAppOpsDir.delete(); 965 } 966 return 0; 967 } 968 collectHistoricalOpsDLocked(@onNull HistoricalOps currentOps, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeingMillis, long filterEndMillis, @OpFlags int filterFlags)969 private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps, int filterUid, 970 @Nullable String filterPackageName, @Nullable String filterAttributionTag, 971 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, 972 long filterBeingMillis, long filterEndMillis, @OpFlags int filterFlags) { 973 final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid, 974 filterPackageName, filterAttributionTag, filterOpNames, filter, 975 filterBeingMillis, filterEndMillis, filterFlags); 976 if (readOps != null) { 977 final int readCount = readOps.size(); 978 for (int i = 0; i < readCount; i++) { 979 final HistoricalOps readOp = readOps.get(i); 980 currentOps.merge(readOp); 981 } 982 } 983 } 984 collectHistoricalOpsBaseDLocked(int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags)985 private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(int filterUid, 986 @Nullable String filterPackageName, @Nullable String filterAttributionTag, 987 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, 988 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags) { 989 File baseDir = null; 990 try { 991 baseDir = sHistoricalAppOpsDir.startRead(); 992 final HistoricalFilesInvariant filesInvariant; 993 if (DEBUG) { 994 filesInvariant = new HistoricalFilesInvariant(); 995 filesInvariant.startTracking(baseDir); 996 } 997 final Set<String> historyFiles = getHistoricalFileNames(baseDir); 998 final long[] globalContentOffsetMillis = {0}; 999 final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked( 1000 baseDir, filterUid, filterPackageName, filterAttributionTag, filterOpNames, 1001 filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags, 1002 globalContentOffsetMillis, null /*outOps*/, 0 /*depth*/, historyFiles); 1003 if (DEBUG) { 1004 filesInvariant.stopTracking(baseDir); 1005 } 1006 sHistoricalAppOpsDir.finishRead(); 1007 return ops; 1008 } catch (Throwable t) { 1009 wtf("Error reading historical app ops. Deleting history.", t, baseDir); 1010 sHistoricalAppOpsDir.delete(); 1011 } 1012 return null; 1013 } 1014 collectHistoricalOpsRecursiveDLocked( @onNull File baseDir, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @NonNull long[] globalContentOffsetMillis, @Nullable LinkedList<HistoricalOps> outOps, int depth, @NonNull Set<String> historyFiles)1015 private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked( 1016 @NonNull File baseDir, int filterUid, @Nullable String filterPackageName, 1017 @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, 1018 @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, 1019 long filterEndTimeMillis, @OpFlags int filterFlags, 1020 @NonNull long[] globalContentOffsetMillis, 1021 @Nullable LinkedList<HistoricalOps> outOps, int depth, 1022 @NonNull Set<String> historyFiles) 1023 throws IOException, XmlPullParserException { 1024 final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier, 1025 depth) * mBaseSnapshotInterval; 1026 final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier, 1027 depth + 1) * mBaseSnapshotInterval; 1028 1029 filterBeginTimeMillis = Math.max(filterBeginTimeMillis - previousIntervalEndMillis, 0); 1030 filterEndTimeMillis = filterEndTimeMillis - previousIntervalEndMillis; 1031 1032 // Read historical data at this level 1033 final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir, 1034 previousIntervalEndMillis, currentIntervalEndMillis, filterUid, 1035 filterPackageName, filterAttributionTag, filterOpNames, filter, 1036 filterBeginTimeMillis, filterEndTimeMillis, filterFlags, 1037 globalContentOffsetMillis, depth, historyFiles); 1038 // Empty is a special signal to stop diving 1039 if (readOps != null && readOps.isEmpty()) { 1040 return outOps; 1041 } 1042 1043 // Collect older historical data from subsequent levels 1044 outOps = collectHistoricalOpsRecursiveDLocked(baseDir, filterUid, filterPackageName, 1045 filterAttributionTag, filterOpNames, filter, filterBeginTimeMillis, 1046 filterEndTimeMillis, filterFlags, globalContentOffsetMillis, outOps, depth + 1, 1047 historyFiles); 1048 1049 // Make older historical data relative to the current historical level 1050 if (outOps != null) { 1051 final int opCount = outOps.size(); 1052 for (int i = 0; i < opCount; i++) { 1053 final HistoricalOps collectedOp = outOps.get(i); 1054 collectedOp.offsetBeginAndEndTime(currentIntervalEndMillis); 1055 } 1056 } 1057 1058 if (readOps != null) { 1059 if (outOps == null) { 1060 outOps = new LinkedList<>(); 1061 } 1062 // Add the read ops to output 1063 final int opCount = readOps.size(); 1064 for (int i = opCount - 1; i >= 0; i--) { 1065 outOps.offerFirst(readOps.get(i)); 1066 } 1067 } 1068 1069 return outOps; 1070 } 1071 handlePersistHistoricalOpsRecursiveDLocked(@onNull File newBaseDir, @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps, @NonNull Set<String> oldFileNames, int depth)1072 private void handlePersistHistoricalOpsRecursiveDLocked(@NonNull File newBaseDir, 1073 @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps, 1074 @NonNull Set<String> oldFileNames, int depth) 1075 throws IOException, XmlPullParserException { 1076 final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier, 1077 depth) * mBaseSnapshotInterval; 1078 final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier, 1079 depth + 1) * mBaseSnapshotInterval; 1080 1081 if (passedOps == null || passedOps.isEmpty()) { 1082 if (!oldFileNames.isEmpty()) { 1083 // If there is an old file we need to copy it over to the new state. 1084 final File oldFile = generateFile(oldBaseDir, depth); 1085 if (oldFileNames.remove(oldFile.getName())) { 1086 final File newFile = generateFile(newBaseDir, depth); 1087 Files.createLink(newFile.toPath(), oldFile.toPath()); 1088 } 1089 handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, 1090 passedOps, oldFileNames, depth + 1); 1091 } 1092 return; 1093 } 1094 1095 if (DEBUG) { 1096 enforceOpsWellFormed(passedOps); 1097 } 1098 1099 // Normalize passed ops time to be based off this interval start 1100 final int passedOpCount = passedOps.size(); 1101 for (int i = 0; i < passedOpCount; i++) { 1102 final HistoricalOps passedOp = passedOps.get(i); 1103 passedOp.offsetBeginAndEndTime(-previousIntervalEndMillis); 1104 } 1105 1106 if (DEBUG) { 1107 enforceOpsWellFormed(passedOps); 1108 } 1109 1110 // Read persisted ops for this interval 1111 final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir, 1112 previousIntervalEndMillis, currentIntervalEndMillis, 1113 Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/, 1114 null /*filterAttributionTag*/, null /*filterOpNames*/, 0 /*filter*/, 1115 Long.MIN_VALUE /*filterBeginTimeMillis*/, 1116 Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL, null, depth, 1117 null /*historyFiles*/); 1118 if (DEBUG) { 1119 enforceOpsWellFormed(existingOps); 1120 } 1121 1122 // Offset existing ops to account for elapsed time 1123 if (existingOps != null) { 1124 final int existingOpCount = existingOps.size(); 1125 if (existingOpCount > 0) { 1126 // Compute elapsed time 1127 final long elapsedTimeMillis = passedOps.get(passedOps.size() - 1) 1128 .getEndTimeMillis(); 1129 for (int i = 0; i < existingOpCount; i++) { 1130 final HistoricalOps existingOp = existingOps.get(i); 1131 existingOp.offsetBeginAndEndTime(elapsedTimeMillis); 1132 } 1133 } 1134 } 1135 1136 if (DEBUG) { 1137 enforceOpsWellFormed(existingOps); 1138 } 1139 1140 final long slotDurationMillis = previousIntervalEndMillis; 1141 1142 // Consolidate passed ops at the current slot duration ensuring each snapshot is 1143 // full. To achieve this we put all passed and existing ops in a list and will 1144 // merge them to ensure each represents a snapshot at the current granularity. 1145 final List<HistoricalOps> allOps = new LinkedList<>(passedOps); 1146 if (existingOps != null) { 1147 allOps.addAll(existingOps); 1148 } 1149 1150 if (DEBUG) { 1151 enforceOpsWellFormed(allOps); 1152 } 1153 1154 // Compute ops to persist and overflow ops 1155 List<HistoricalOps> persistedOps = null; 1156 List<HistoricalOps> overflowedOps = null; 1157 1158 // We move a snapshot into the next level only if the start time is 1159 // after the end of the current interval. This avoids rewriting all 1160 // files to propagate the information added to the history on every 1161 // iteration. Instead, we would rewrite the next level file only if 1162 // an entire snapshot from the previous level is being propagated. 1163 // The trade off is that we need to store how much the last snapshot 1164 // of the current interval overflows past the interval end. We write 1165 // the overflow data to avoid parsing all snapshots on query. 1166 long intervalOverflowMillis = 0; 1167 final int opCount = allOps.size(); 1168 for (int i = 0; i < opCount; i++) { 1169 final HistoricalOps op = allOps.get(i); 1170 final HistoricalOps persistedOp; 1171 final HistoricalOps overflowedOp; 1172 if (op.getEndTimeMillis() <= currentIntervalEndMillis) { 1173 persistedOp = op; 1174 overflowedOp = null; 1175 } else if (op.getBeginTimeMillis() < currentIntervalEndMillis) { 1176 persistedOp = op; 1177 intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis; 1178 if (intervalOverflowMillis > previousIntervalEndMillis) { 1179 final double splitScale = (double) intervalOverflowMillis 1180 / op.getDurationMillis(); 1181 overflowedOp = spliceFromEnd(op, splitScale); 1182 intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis; 1183 } else { 1184 overflowedOp = null; 1185 } 1186 } else { 1187 persistedOp = null; 1188 overflowedOp = op; 1189 } 1190 if (persistedOp != null) { 1191 if (persistedOps == null) { 1192 persistedOps = new ArrayList<>(); 1193 } 1194 persistedOps.add(persistedOp); 1195 } 1196 if (overflowedOp != null) { 1197 if (overflowedOps == null) { 1198 overflowedOps = new ArrayList<>(); 1199 } 1200 overflowedOps.add(overflowedOp); 1201 } 1202 } 1203 1204 if (DEBUG) { 1205 enforceOpsWellFormed(persistedOps); 1206 enforceOpsWellFormed(overflowedOps); 1207 } 1208 1209 final File newFile = generateFile(newBaseDir, depth); 1210 oldFileNames.remove(newFile.getName()); 1211 1212 if (persistedOps != null) { 1213 normalizeSnapshotForSlotDuration(persistedOps, slotDurationMillis); 1214 writeHistoricalOpsDLocked(persistedOps, intervalOverflowMillis, newFile); 1215 if (DEBUG) { 1216 Slog.i(LOG_TAG, "Persisted at depth: " + depth + " file: " + newFile 1217 + " ops:\n" + opsToDebugString(persistedOps)); 1218 enforceOpsWellFormed(persistedOps); 1219 } 1220 } 1221 1222 handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, 1223 overflowedOps, oldFileNames, depth + 1); 1224 } 1225 readHistoricalOpsLocked(File baseDir, long intervalBeginMillis, long intervalEndMillis, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @Nullable long[] cumulativeOverflowMillis, int depth, @NonNull Set<String> historyFiles)1226 private @Nullable List<HistoricalOps> readHistoricalOpsLocked(File baseDir, 1227 long intervalBeginMillis, long intervalEndMillis, int filterUid, 1228 @Nullable String filterPackageName, @Nullable String filterAttributionTag, 1229 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, 1230 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, 1231 @Nullable long[] cumulativeOverflowMillis, int depth, 1232 @NonNull Set<String> historyFiles) 1233 throws IOException, XmlPullParserException { 1234 final File file = generateFile(baseDir, depth); 1235 if (historyFiles != null) { 1236 historyFiles.remove(file.getName()); 1237 } 1238 if (filterBeginTimeMillis >= filterEndTimeMillis 1239 || filterEndTimeMillis < intervalBeginMillis) { 1240 // Don't go deeper 1241 return Collections.emptyList(); 1242 } 1243 if (filterBeginTimeMillis >= (intervalEndMillis 1244 + ((intervalEndMillis - intervalBeginMillis) / mIntervalCompressionMultiplier) 1245 + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0)) 1246 || !file.exists()) { 1247 if (historyFiles == null || historyFiles.isEmpty()) { 1248 // Don't go deeper 1249 return Collections.emptyList(); 1250 } else { 1251 // Keep diving 1252 return null; 1253 } 1254 } 1255 return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterAttributionTag, 1256 filterOpNames, filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags, 1257 cumulativeOverflowMillis); 1258 } 1259 readHistoricalOpsLocked(@onNull File file, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @Nullable long[] cumulativeOverflowMillis)1260 private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file, 1261 int filterUid, @Nullable String filterPackageName, 1262 @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, 1263 @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, 1264 long filterEndTimeMillis, @OpFlags int filterFlags, 1265 @Nullable long[] cumulativeOverflowMillis) 1266 throws IOException, XmlPullParserException { 1267 if (DEBUG) { 1268 Slog.i(LOG_TAG, "Reading ops from:" + file); 1269 } 1270 List<HistoricalOps> allOps = null; 1271 try (FileInputStream stream = new FileInputStream(file)) { 1272 final TypedXmlPullParser parser = Xml.resolvePullParser(stream); 1273 XmlUtils.beginDocument(parser, TAG_HISTORY); 1274 1275 // We haven't released version 1 and have more detailed 1276 // accounting - just nuke the current state 1277 final int version = parser.getAttributeInt(null, ATTR_VERSION); 1278 if (CURRENT_VERSION == 2 && version < CURRENT_VERSION) { 1279 throw new IllegalStateException("Dropping unsupported history " 1280 + "version 1 for file:" + file); 1281 } 1282 1283 final long overflowMillis = parser.getAttributeLong(null, ATTR_OVERFLOW, 0); 1284 final int depth = parser.getDepth(); 1285 while (XmlUtils.nextElementWithin(parser, depth)) { 1286 if (TAG_OPS.equals(parser.getName())) { 1287 final HistoricalOps ops = readeHistoricalOpsDLocked(parser, filterUid, 1288 filterPackageName, filterAttributionTag, filterOpNames, filter, 1289 filterBeginTimeMillis, filterEndTimeMillis, filterFlags, 1290 cumulativeOverflowMillis); 1291 if (ops == null) { 1292 continue; 1293 } 1294 if (ops.isEmpty()) { 1295 XmlUtils.skipCurrentTag(parser); 1296 continue; 1297 } 1298 if (allOps == null) { 1299 allOps = new ArrayList<>(); 1300 } 1301 allOps.add(ops); 1302 } 1303 } 1304 if (cumulativeOverflowMillis != null) { 1305 cumulativeOverflowMillis[0] += overflowMillis; 1306 } 1307 } catch (FileNotFoundException e) { 1308 Slog.i(LOG_TAG, "No history file: " + file.getName()); 1309 return Collections.emptyList(); 1310 } 1311 if (DEBUG) { 1312 if (allOps != null) { 1313 Slog.i(LOG_TAG, "Read from file: " + file + " ops:\n" 1314 + opsToDebugString(allOps)); 1315 enforceOpsWellFormed(allOps); 1316 } 1317 } 1318 return allOps; 1319 } 1320 readeHistoricalOpsDLocked( @onNull TypedXmlPullParser parser, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @Nullable long[] cumulativeOverflowMillis)1321 private @Nullable HistoricalOps readeHistoricalOpsDLocked( 1322 @NonNull TypedXmlPullParser parser, int filterUid, 1323 @Nullable String filterPackageName, 1324 @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, 1325 @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, 1326 long filterEndTimeMillis, @OpFlags int filterFlags, 1327 @Nullable long[] cumulativeOverflowMillis) 1328 throws IOException, XmlPullParserException { 1329 final long beginTimeMillis = parser.getAttributeLong(null, ATTR_BEGIN_TIME, 0) 1330 + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0); 1331 final long endTimeMillis = parser.getAttributeLong(null, ATTR_END_TIME, 0) 1332 + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0); 1333 // Keep reading as subsequent records may start matching 1334 if (filterEndTimeMillis < beginTimeMillis) { 1335 return null; 1336 } 1337 // Stop reading as subsequent records will not match 1338 if (filterBeginTimeMillis > endTimeMillis) { 1339 return new HistoricalOps(0, 0); 1340 } 1341 final long filteredBeginTimeMillis = Math.max(beginTimeMillis, filterBeginTimeMillis); 1342 final long filteredEndTimeMillis = Math.min(endTimeMillis, filterEndTimeMillis); 1343 // // Keep reading as subsequent records may start matching 1344 // if (filteredEndTimeMillis - filterBeginTimeMillis <= 0) { 1345 // return null; 1346 // } 1347 final double filterScale = (double) (filteredEndTimeMillis - filteredBeginTimeMillis) 1348 / (double) (endTimeMillis - beginTimeMillis); 1349 HistoricalOps ops = null; 1350 final int depth = parser.getDepth(); 1351 while (XmlUtils.nextElementWithin(parser, depth)) { 1352 if (TAG_UID.equals(parser.getName())) { 1353 final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser, 1354 filterUid, filterPackageName, filterAttributionTag, filterOpNames, 1355 filter, filterFlags, filterScale); 1356 if (ops == null) { 1357 ops = returnedOps; 1358 } 1359 } 1360 } 1361 if (ops != null) { 1362 ops.setBeginAndEndTime(filteredBeginTimeMillis, filteredEndTimeMillis); 1363 } 1364 return ops; 1365 } 1366 readHistoricalUidOpsDLocked( @ullable HistoricalOps ops, @NonNull TypedXmlPullParser parser, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, double filterScale)1367 private @Nullable HistoricalOps readHistoricalUidOpsDLocked( 1368 @Nullable HistoricalOps ops, @NonNull TypedXmlPullParser parser, int filterUid, 1369 @Nullable String filterPackageName, @Nullable String filterAttributionTag, 1370 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, 1371 @OpFlags int filterFlags, double filterScale) 1372 throws IOException, XmlPullParserException { 1373 final int uid = parser.getAttributeInt(null, ATTR_NAME); 1374 if ((filter & FILTER_BY_UID) != 0 && filterUid != uid) { 1375 XmlUtils.skipCurrentTag(parser); 1376 return null; 1377 } 1378 final int depth = parser.getDepth(); 1379 while (XmlUtils.nextElementWithin(parser, depth)) { 1380 if (TAG_PACKAGE.equals(parser.getName())) { 1381 final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops, uid, 1382 parser, filterPackageName, filterAttributionTag, filterOpNames, filter, 1383 filterFlags, filterScale); 1384 if (ops == null) { 1385 ops = returnedOps; 1386 } 1387 } 1388 } 1389 return ops; 1390 } 1391 readHistoricalPackageOpsDLocked( @ullable HistoricalOps ops, int uid, @NonNull TypedXmlPullParser parser, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, double filterScale)1392 private @Nullable HistoricalOps readHistoricalPackageOpsDLocked( 1393 @Nullable HistoricalOps ops, int uid, @NonNull TypedXmlPullParser parser, 1394 @Nullable String filterPackageName, @Nullable String filterAttributionTag, 1395 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, 1396 @OpFlags int filterFlags, double filterScale) 1397 throws IOException, XmlPullParserException { 1398 final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME); 1399 if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !filterPackageName.equals(packageName)) { 1400 XmlUtils.skipCurrentTag(parser); 1401 return null; 1402 } 1403 final int depth = parser.getDepth(); 1404 while (XmlUtils.nextElementWithin(parser, depth)) { 1405 if (TAG_ATTRIBUTION.equals(parser.getName())) { 1406 final HistoricalOps returnedOps = readHistoricalAttributionOpsDLocked(ops, uid, 1407 packageName, parser, filterAttributionTag, filterOpNames, filter, 1408 filterFlags, filterScale); 1409 if (ops == null) { 1410 ops = returnedOps; 1411 } 1412 } 1413 } 1414 return ops; 1415 } 1416 readHistoricalAttributionOpsDLocked( @ullable HistoricalOps ops, int uid, String packageName, @NonNull TypedXmlPullParser parser, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, double filterScale)1417 private @Nullable HistoricalOps readHistoricalAttributionOpsDLocked( 1418 @Nullable HistoricalOps ops, int uid, String packageName, 1419 @NonNull TypedXmlPullParser parser, @Nullable String filterAttributionTag, 1420 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, 1421 @OpFlags int filterFlags, double filterScale) 1422 throws IOException, XmlPullParserException { 1423 final String attributionTag = XmlUtils.readStringAttribute(parser, ATTR_NAME); 1424 if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(filterAttributionTag, 1425 attributionTag)) { 1426 XmlUtils.skipCurrentTag(parser); 1427 return null; 1428 } 1429 final int depth = parser.getDepth(); 1430 while (XmlUtils.nextElementWithin(parser, depth)) { 1431 if (TAG_OP.equals(parser.getName())) { 1432 final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid, packageName, 1433 attributionTag, parser, filterOpNames, filter, filterFlags, 1434 filterScale); 1435 if (ops == null) { 1436 ops = returnedOps; 1437 } 1438 } 1439 } 1440 return ops; 1441 } 1442 readHistoricalOpDLocked(@ullable HistoricalOps ops, int uid, @NonNull String packageName, @Nullable String attributionTag, @NonNull TypedXmlPullParser parser, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, double filterScale)1443 private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops, 1444 int uid, @NonNull String packageName, @Nullable String attributionTag, 1445 @NonNull TypedXmlPullParser parser, @Nullable String[] filterOpNames, 1446 @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, 1447 double filterScale) 1448 throws IOException, XmlPullParserException { 1449 final int op = parser.getAttributeInt(null, ATTR_NAME); 1450 if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(filterOpNames, 1451 AppOpsManager.opToPublicName(op))) { 1452 XmlUtils.skipCurrentTag(parser); 1453 return null; 1454 } 1455 final int depth = parser.getDepth(); 1456 while (XmlUtils.nextElementWithin(parser, depth)) { 1457 if (TAG_STATE.equals(parser.getName())) { 1458 final HistoricalOps returnedOps = readStateDLocked(ops, uid, 1459 packageName, attributionTag, op, parser, filterFlags, filterScale); 1460 if (ops == null) { 1461 ops = returnedOps; 1462 } 1463 } 1464 } 1465 return ops; 1466 } 1467 readStateDLocked(@ullable HistoricalOps ops, int uid, @NonNull String packageName, @Nullable String attributionTag, int op, @NonNull TypedXmlPullParser parser, @OpFlags int filterFlags, double filterScale)1468 private @Nullable HistoricalOps readStateDLocked(@Nullable HistoricalOps ops, 1469 int uid, @NonNull String packageName, @Nullable String attributionTag, int op, 1470 @NonNull TypedXmlPullParser parser, @OpFlags int filterFlags, double filterScale) 1471 throws IOException, XmlPullParserException { 1472 final long key = parser.getAttributeLong(null, ATTR_NAME); 1473 final int flags = AppOpsManager.extractFlagsFromKey(key) & filterFlags; 1474 if (flags == 0) { 1475 return null; 1476 } 1477 final int uidState = AppOpsManager.extractUidStateFromKey(key); 1478 long accessCount = parser.getAttributeLong(null, ATTR_ACCESS_COUNT, 0); 1479 if (accessCount > 0) { 1480 if (!Double.isNaN(filterScale)) { 1481 accessCount = (long) HistoricalOps.round( 1482 (double) accessCount * filterScale); 1483 } 1484 if (ops == null) { 1485 ops = new HistoricalOps(0, 0); 1486 } 1487 ops.increaseAccessCount(op, uid, packageName, attributionTag, uidState, flags, 1488 accessCount); 1489 } 1490 long rejectCount = parser.getAttributeLong(null, ATTR_REJECT_COUNT, 0); 1491 if (rejectCount > 0) { 1492 if (!Double.isNaN(filterScale)) { 1493 rejectCount = (long) HistoricalOps.round( 1494 (double) rejectCount * filterScale); 1495 } 1496 if (ops == null) { 1497 ops = new HistoricalOps(0, 0); 1498 } 1499 ops.increaseRejectCount(op, uid, packageName, attributionTag, uidState, flags, 1500 rejectCount); 1501 } 1502 long accessDuration = parser.getAttributeLong(null, ATTR_ACCESS_DURATION, 0); 1503 if (accessDuration > 0) { 1504 if (!Double.isNaN(filterScale)) { 1505 accessDuration = (long) HistoricalOps.round( 1506 (double) accessDuration * filterScale); 1507 } 1508 if (ops == null) { 1509 ops = new HistoricalOps(0, 0); 1510 } 1511 ops.increaseAccessDuration(op, uid, packageName, attributionTag, uidState, flags, 1512 accessDuration); 1513 } 1514 return ops; 1515 } 1516 writeHistoricalOpsDLocked(@ullable List<HistoricalOps> allOps, long intervalOverflowMillis, @NonNull File file)1517 private void writeHistoricalOpsDLocked(@Nullable List<HistoricalOps> allOps, 1518 long intervalOverflowMillis, @NonNull File file) throws IOException { 1519 final FileOutputStream output = sHistoricalAppOpsDir.openWrite(file); 1520 try { 1521 final TypedXmlSerializer serializer = Xml.resolveSerializer(output); 1522 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", 1523 true); 1524 serializer.startDocument(null, true); 1525 serializer.startTag(null, TAG_HISTORY); 1526 serializer.attributeInt(null, ATTR_VERSION, CURRENT_VERSION); 1527 if (intervalOverflowMillis != 0) { 1528 serializer.attributeLong(null, ATTR_OVERFLOW, intervalOverflowMillis); 1529 } 1530 if (allOps != null) { 1531 final int opsCount = allOps.size(); 1532 for (int i = 0; i < opsCount; i++) { 1533 final HistoricalOps ops = allOps.get(i); 1534 writeHistoricalOpDLocked(ops, serializer); 1535 } 1536 } 1537 serializer.endTag(null, TAG_HISTORY); 1538 serializer.endDocument(); 1539 sHistoricalAppOpsDir.closeWrite(output); 1540 } catch (IOException e) { 1541 sHistoricalAppOpsDir.failWrite(output); 1542 throw e; 1543 } 1544 } 1545 writeHistoricalOpDLocked(@onNull HistoricalOps ops, @NonNull TypedXmlSerializer serializer)1546 private void writeHistoricalOpDLocked(@NonNull HistoricalOps ops, 1547 @NonNull TypedXmlSerializer serializer) throws IOException { 1548 serializer.startTag(null, TAG_OPS); 1549 serializer.attributeLong(null, ATTR_BEGIN_TIME, ops.getBeginTimeMillis()); 1550 serializer.attributeLong(null, ATTR_END_TIME, ops.getEndTimeMillis()); 1551 final int uidCount = ops.getUidCount(); 1552 for (int i = 0; i < uidCount; i++) { 1553 final HistoricalUidOps uidOp = ops.getUidOpsAt(i); 1554 writeHistoricalUidOpsDLocked(uidOp, serializer); 1555 } 1556 serializer.endTag(null, TAG_OPS); 1557 } 1558 writeHistoricalUidOpsDLocked(@onNull HistoricalUidOps uidOps, @NonNull TypedXmlSerializer serializer)1559 private void writeHistoricalUidOpsDLocked(@NonNull HistoricalUidOps uidOps, 1560 @NonNull TypedXmlSerializer serializer) throws IOException { 1561 serializer.startTag(null, TAG_UID); 1562 serializer.attributeInt(null, ATTR_NAME, uidOps.getUid()); 1563 final int packageCount = uidOps.getPackageCount(); 1564 for (int i = 0; i < packageCount; i++) { 1565 final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(i); 1566 writeHistoricalPackageOpsDLocked(packageOps, serializer); 1567 } 1568 serializer.endTag(null, TAG_UID); 1569 } 1570 writeHistoricalPackageOpsDLocked(@onNull HistoricalPackageOps packageOps, @NonNull TypedXmlSerializer serializer)1571 private void writeHistoricalPackageOpsDLocked(@NonNull HistoricalPackageOps packageOps, 1572 @NonNull TypedXmlSerializer serializer) throws IOException { 1573 serializer.startTag(null, TAG_PACKAGE); 1574 serializer.attributeInterned(null, ATTR_NAME, packageOps.getPackageName()); 1575 final int numAttributions = packageOps.getAttributedOpsCount(); 1576 for (int i = 0; i < numAttributions; i++) { 1577 final AppOpsManager.AttributedHistoricalOps op = packageOps.getAttributedOpsAt(i); 1578 writeHistoricalAttributionOpsDLocked(op, serializer); 1579 } 1580 serializer.endTag(null, TAG_PACKAGE); 1581 } 1582 writeHistoricalAttributionOpsDLocked( @onNull AppOpsManager.AttributedHistoricalOps attributionOps, @NonNull TypedXmlSerializer serializer)1583 private void writeHistoricalAttributionOpsDLocked( 1584 @NonNull AppOpsManager.AttributedHistoricalOps attributionOps, 1585 @NonNull TypedXmlSerializer serializer) throws IOException { 1586 serializer.startTag(null, TAG_ATTRIBUTION); 1587 XmlUtils.writeStringAttribute(serializer, ATTR_NAME, attributionOps.getTag()); 1588 final int opCount = attributionOps.getOpCount(); 1589 for (int i = 0; i < opCount; i++) { 1590 final HistoricalOp op = attributionOps.getOpAt(i); 1591 writeHistoricalOpDLocked(op, serializer); 1592 } 1593 serializer.endTag(null, TAG_ATTRIBUTION); 1594 } 1595 writeHistoricalOpDLocked(@onNull HistoricalOp op, @NonNull TypedXmlSerializer serializer)1596 private void writeHistoricalOpDLocked(@NonNull HistoricalOp op, 1597 @NonNull TypedXmlSerializer serializer) throws IOException { 1598 final LongSparseArray keys = op.collectKeys(); 1599 if (keys == null || keys.size() <= 0) { 1600 return; 1601 } 1602 serializer.startTag(null, TAG_OP); 1603 serializer.attributeInt(null, ATTR_NAME, op.getOpCode()); 1604 final int keyCount = keys.size(); 1605 for (int i = 0; i < keyCount; i++) { 1606 writeStateOnLocked(op, keys.keyAt(i), serializer); 1607 } 1608 serializer.endTag(null, TAG_OP); 1609 } 1610 writeStateOnLocked(@onNull HistoricalOp op, long key, @NonNull TypedXmlSerializer serializer)1611 private void writeStateOnLocked(@NonNull HistoricalOp op, long key, 1612 @NonNull TypedXmlSerializer serializer) throws IOException { 1613 final int uidState = AppOpsManager.extractUidStateFromKey(key); 1614 final int flags = AppOpsManager.extractFlagsFromKey(key); 1615 1616 final long accessCount = op.getAccessCount(uidState, uidState, flags); 1617 final long rejectCount = op.getRejectCount(uidState, uidState, flags); 1618 final long accessDuration = op.getAccessDuration(uidState, uidState, flags); 1619 1620 if (accessCount <= 0 && rejectCount <= 0 && accessDuration <= 0) { 1621 return; 1622 } 1623 1624 serializer.startTag(null, TAG_STATE); 1625 serializer.attributeLong(null, ATTR_NAME, key); 1626 if (accessCount > 0) { 1627 serializer.attributeLong(null, ATTR_ACCESS_COUNT, accessCount); 1628 } 1629 if (rejectCount > 0) { 1630 serializer.attributeLong(null, ATTR_REJECT_COUNT, rejectCount); 1631 } 1632 if (accessDuration > 0) { 1633 serializer.attributeLong(null, ATTR_ACCESS_DURATION, accessDuration); 1634 } 1635 serializer.endTag(null, TAG_STATE); 1636 } 1637 enforceOpsWellFormed(@onNull List<HistoricalOps> ops)1638 private static void enforceOpsWellFormed(@NonNull List<HistoricalOps> ops) { 1639 if (ops == null) { 1640 return; 1641 } 1642 HistoricalOps previous; 1643 HistoricalOps current = null; 1644 final int opsCount = ops.size(); 1645 for (int i = 0; i < opsCount; i++) { 1646 previous = current; 1647 current = ops.get(i); 1648 if (current.isEmpty()) { 1649 throw new IllegalStateException("Empty ops:\n" 1650 + opsToDebugString(ops)); 1651 } 1652 if (current.getEndTimeMillis() < current.getBeginTimeMillis()) { 1653 throw new IllegalStateException("Begin after end:\n" 1654 + opsToDebugString(ops)); 1655 } 1656 if (previous != null) { 1657 if (previous.getEndTimeMillis() > current.getBeginTimeMillis()) { 1658 throw new IllegalStateException("Intersecting ops:\n" 1659 + opsToDebugString(ops)); 1660 } 1661 if (previous.getBeginTimeMillis() > current.getBeginTimeMillis()) { 1662 throw new IllegalStateException("Non increasing ops:\n" 1663 + opsToDebugString(ops)); 1664 } 1665 } 1666 } 1667 } 1668 computeGlobalIntervalBeginMillis(int depth)1669 private long computeGlobalIntervalBeginMillis(int depth) { 1670 long beginTimeMillis = 0; 1671 for (int i = 0; i < depth + 1; i++) { 1672 beginTimeMillis += Math.pow(mIntervalCompressionMultiplier, i); 1673 } 1674 return beginTimeMillis * mBaseSnapshotInterval; 1675 } 1676 spliceFromEnd(@onNull HistoricalOps ops, double spliceRatio)1677 private static @NonNull HistoricalOps spliceFromEnd(@NonNull HistoricalOps ops, 1678 double spliceRatio) { 1679 if (DEBUG) { 1680 Slog.w(LOG_TAG, "Splicing from end:" + ops + " ratio:" + spliceRatio); 1681 } 1682 final HistoricalOps splice = ops.spliceFromEnd(spliceRatio); 1683 if (DEBUG) { 1684 Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice); 1685 } 1686 return splice; 1687 } 1688 1689 spliceFromBeginning(@onNull HistoricalOps ops, double spliceRatio)1690 private static @NonNull HistoricalOps spliceFromBeginning(@NonNull HistoricalOps ops, 1691 double spliceRatio) { 1692 if (DEBUG) { 1693 Slog.w(LOG_TAG, "Splicing from beginning:" + ops + " ratio:" + spliceRatio); 1694 } 1695 final HistoricalOps splice = ops.spliceFromBeginning(spliceRatio); 1696 if (DEBUG) { 1697 Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice); 1698 } 1699 return splice; 1700 } 1701 normalizeSnapshotForSlotDuration(@onNull List<HistoricalOps> ops, long slotDurationMillis)1702 private static void normalizeSnapshotForSlotDuration(@NonNull List<HistoricalOps> ops, 1703 long slotDurationMillis) { 1704 if (DEBUG) { 1705 Slog.i(LOG_TAG, "Normalizing for slot duration: " + slotDurationMillis 1706 + " ops:\n" + opsToDebugString(ops)); 1707 enforceOpsWellFormed(ops); 1708 } 1709 long slotBeginTimeMillis; 1710 final int opCount = ops.size(); 1711 for (int processedIdx = opCount - 1; processedIdx >= 0; processedIdx--) { 1712 final HistoricalOps processedOp = ops.get(processedIdx); 1713 slotBeginTimeMillis = Math.max(processedOp.getEndTimeMillis() 1714 - slotDurationMillis, 0); 1715 for (int candidateIdx = processedIdx - 1; candidateIdx >= 0; candidateIdx--) { 1716 final HistoricalOps candidateOp = ops.get(candidateIdx); 1717 final long candidateSlotIntersectionMillis = candidateOp.getEndTimeMillis() 1718 - Math.min(slotBeginTimeMillis, processedOp.getBeginTimeMillis()); 1719 if (candidateSlotIntersectionMillis <= 0) { 1720 break; 1721 } 1722 final float candidateSplitRatio = candidateSlotIntersectionMillis 1723 / (float) candidateOp.getDurationMillis(); 1724 if (Float.compare(candidateSplitRatio, 1.0f) >= 0) { 1725 ops.remove(candidateIdx); 1726 processedIdx--; 1727 processedOp.merge(candidateOp); 1728 } else { 1729 final HistoricalOps endSplice = spliceFromEnd(candidateOp, 1730 candidateSplitRatio); 1731 if (endSplice != null) { 1732 processedOp.merge(endSplice); 1733 } 1734 if (candidateOp.isEmpty()) { 1735 ops.remove(candidateIdx); 1736 processedIdx--; 1737 } 1738 } 1739 } 1740 } 1741 if (DEBUG) { 1742 Slog.i(LOG_TAG, "Normalized for slot duration: " + slotDurationMillis 1743 + " ops:\n" + opsToDebugString(ops)); 1744 enforceOpsWellFormed(ops); 1745 } 1746 } 1747 opsToDebugString(@onNull List<HistoricalOps> ops)1748 private static @NonNull String opsToDebugString(@NonNull List<HistoricalOps> ops) { 1749 StringBuilder builder = new StringBuilder(); 1750 final int opCount = ops.size(); 1751 for (int i = 0; i < opCount; i++) { 1752 builder.append(" "); 1753 builder.append(ops.get(i)); 1754 if (i < opCount - 1) { 1755 builder.append('\n'); 1756 } 1757 } 1758 return builder.toString(); 1759 } 1760 getHistoricalFileNames(@onNull File historyDir)1761 private static Set<String> getHistoricalFileNames(@NonNull File historyDir) { 1762 final File[] files = historyDir.listFiles(); 1763 if (files == null) { 1764 return Collections.emptySet(); 1765 } 1766 final ArraySet<String> fileNames = new ArraySet<>(files.length); 1767 for (File file : files) { 1768 fileNames.add(file.getName()); 1769 1770 } 1771 return fileNames; 1772 } 1773 } 1774 1775 private static class HistoricalFilesInvariant { 1776 private final @NonNull List<File> mBeginFiles = new ArrayList<>(); 1777 startTracking(@onNull File folder)1778 public void startTracking(@NonNull File folder) { 1779 final File[] files = folder.listFiles(); 1780 if (files != null) { 1781 Collections.addAll(mBeginFiles, files); 1782 } 1783 } 1784 stopTracking(@onNull File folder)1785 public void stopTracking(@NonNull File folder) { 1786 final List<File> endFiles = new ArrayList<>(); 1787 final File[] files = folder.listFiles(); 1788 if (files != null) { 1789 Collections.addAll(endFiles, files); 1790 } 1791 final long beginOldestFileOffsetMillis = getOldestFileOffsetMillis(mBeginFiles); 1792 final long endOldestFileOffsetMillis = getOldestFileOffsetMillis(endFiles); 1793 if (endOldestFileOffsetMillis < beginOldestFileOffsetMillis) { 1794 final String message = "History loss detected!" 1795 + "\nold files: " + mBeginFiles; 1796 wtf(message, null, folder); 1797 throw new IllegalStateException(message); 1798 } 1799 } 1800 getOldestFileOffsetMillis(@onNull List<File> files)1801 private static long getOldestFileOffsetMillis(@NonNull List<File> files) { 1802 if (files.isEmpty()) { 1803 return 0; 1804 } 1805 String longestName = files.get(0).getName(); 1806 final int fileCount = files.size(); 1807 for (int i = 1; i < fileCount; i++) { 1808 final File file = files.get(i); 1809 if (file.getName().length() > longestName.length()) { 1810 longestName = file.getName(); 1811 } 1812 } 1813 return Long.parseLong(longestName.replace(HISTORY_FILE_SUFFIX, "")); 1814 } 1815 } 1816 1817 private final class StringDumpVisitor implements AppOpsManager.HistoricalOpsVisitor { 1818 private final long mNow = System.currentTimeMillis(); 1819 1820 private final SimpleDateFormat mDateFormatter = new SimpleDateFormat( 1821 "yyyy-MM-dd HH:mm:ss.SSS"); 1822 private final Date mDate = new Date(); 1823 1824 private final @NonNull String mOpsPrefix; 1825 private final @NonNull String mUidPrefix; 1826 private final @NonNull String mPackagePrefix; 1827 private final @NonNull String mAttributionPrefix; 1828 private final @NonNull String mEntryPrefix; 1829 private final @NonNull String mUidStatePrefix; 1830 private final @NonNull PrintWriter mWriter; 1831 private final int mFilterUid; 1832 private final String mFilterPackage; 1833 private final String mFilterAttributionTag; 1834 private final int mFilterOp; 1835 private final @HistoricalOpsRequestFilter int mFilter; 1836 StringDumpVisitor(@onNull String prefix, @NonNull PrintWriter writer, int filterUid, @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp, @HistoricalOpsRequestFilter int filter)1837 StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer, int filterUid, 1838 @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp, 1839 @HistoricalOpsRequestFilter int filter) { 1840 mOpsPrefix = prefix + " "; 1841 mUidPrefix = mOpsPrefix + " "; 1842 mPackagePrefix = mUidPrefix + " "; 1843 mAttributionPrefix = mPackagePrefix + " "; 1844 mEntryPrefix = mAttributionPrefix + " "; 1845 mUidStatePrefix = mEntryPrefix + " "; 1846 mWriter = writer; 1847 mFilterUid = filterUid; 1848 mFilterPackage = filterPackage; 1849 mFilterAttributionTag = filterAttributionTag; 1850 mFilterOp = filterOp; 1851 mFilter = filter; 1852 } 1853 1854 @Override visitHistoricalOps(HistoricalOps ops)1855 public void visitHistoricalOps(HistoricalOps ops) { 1856 mWriter.println(); 1857 mWriter.print(mOpsPrefix); 1858 mWriter.println("snapshot:"); 1859 mWriter.print(mUidPrefix); 1860 mWriter.print("begin = "); 1861 mDate.setTime(ops.getBeginTimeMillis()); 1862 mWriter.print(mDateFormatter.format(mDate)); 1863 mWriter.print(" ("); 1864 TimeUtils.formatDuration(ops.getBeginTimeMillis() - mNow, mWriter); 1865 mWriter.println(")"); 1866 mWriter.print(mUidPrefix); 1867 mWriter.print("end = "); 1868 mDate.setTime(ops.getEndTimeMillis()); 1869 mWriter.print(mDateFormatter.format(mDate)); 1870 mWriter.print(" ("); 1871 TimeUtils.formatDuration(ops.getEndTimeMillis() - mNow, mWriter); 1872 mWriter.println(")"); 1873 } 1874 1875 @Override visitHistoricalUidOps(HistoricalUidOps ops)1876 public void visitHistoricalUidOps(HistoricalUidOps ops) { 1877 if ((mFilter & FILTER_BY_UID) != 0 && mFilterUid != ops.getUid()) { 1878 return; 1879 } 1880 mWriter.println(); 1881 mWriter.print(mUidPrefix); 1882 mWriter.print("Uid "); 1883 UserHandle.formatUid(mWriter, ops.getUid()); 1884 mWriter.println(":"); 1885 } 1886 1887 @Override visitHistoricalPackageOps(HistoricalPackageOps ops)1888 public void visitHistoricalPackageOps(HistoricalPackageOps ops) { 1889 if ((mFilter & FILTER_BY_PACKAGE_NAME) != 0 && !mFilterPackage.equals( 1890 ops.getPackageName())) { 1891 return; 1892 } 1893 mWriter.print(mPackagePrefix); 1894 mWriter.print("Package "); 1895 mWriter.print(ops.getPackageName()); 1896 mWriter.println(":"); 1897 } 1898 1899 @Override visitHistoricalAttributionOps(AppOpsManager.AttributedHistoricalOps ops)1900 public void visitHistoricalAttributionOps(AppOpsManager.AttributedHistoricalOps ops) { 1901 if ((mFilter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(mFilterPackage, 1902 ops.getTag())) { 1903 return; 1904 } 1905 mWriter.print(mAttributionPrefix); 1906 mWriter.print("Attribution "); 1907 mWriter.print(ops.getTag()); 1908 mWriter.println(":"); 1909 } 1910 1911 @Override visitHistoricalOp(HistoricalOp ops)1912 public void visitHistoricalOp(HistoricalOp ops) { 1913 if ((mFilter & FILTER_BY_OP_NAMES) != 0 && mFilterOp != ops.getOpCode()) { 1914 return; 1915 } 1916 mWriter.print(mEntryPrefix); 1917 mWriter.print(AppOpsManager.opToName(ops.getOpCode())); 1918 mWriter.println(":"); 1919 final LongSparseArray keys = ops.collectKeys(); 1920 final int keyCount = keys.size(); 1921 for (int i = 0; i < keyCount; i++) { 1922 final long key = keys.keyAt(i); 1923 final int uidState = AppOpsManager.extractUidStateFromKey(key); 1924 final int flags = AppOpsManager.extractFlagsFromKey(key); 1925 boolean printedUidState = false; 1926 final long accessCount = ops.getAccessCount(uidState, uidState, flags); 1927 if (accessCount > 0) { 1928 if (!printedUidState) { 1929 mWriter.print(mUidStatePrefix); 1930 mWriter.print(AppOpsManager.keyToString(key)); 1931 mWriter.print(" = "); 1932 printedUidState = true; 1933 } 1934 mWriter.print("access="); 1935 mWriter.print(accessCount); 1936 } 1937 final long rejectCount = ops.getRejectCount(uidState, uidState, flags); 1938 if (rejectCount > 0) { 1939 if (!printedUidState) { 1940 mWriter.print(mUidStatePrefix); 1941 mWriter.print(AppOpsManager.keyToString(key)); 1942 mWriter.print(" = "); 1943 printedUidState = true; 1944 } else { 1945 mWriter.print(", "); 1946 } 1947 mWriter.print("reject="); 1948 mWriter.print(rejectCount); 1949 } 1950 final long accessDuration = ops.getAccessDuration(uidState, uidState, flags); 1951 if (accessDuration > 0) { 1952 if (!printedUidState) { 1953 mWriter.print(mUidStatePrefix); 1954 mWriter.print(AppOpsManager.keyToString(key)); 1955 mWriter.print(" = "); 1956 printedUidState = true; 1957 } else { 1958 mWriter.print(", "); 1959 } 1960 mWriter.print("duration="); 1961 TimeUtils.formatDuration(accessDuration, mWriter); 1962 } 1963 if (printedUidState) { 1964 mWriter.println(""); 1965 } 1966 } 1967 } 1968 } 1969 wtf(@ullable String message, @Nullable Throwable t, @Nullable File storage)1970 private static void wtf(@Nullable String message, @Nullable Throwable t, 1971 @Nullable File storage) { 1972 Slog.wtf(LOG_TAG, message, t); 1973 if (KEEP_WTF_LOG) { 1974 try { 1975 final File file = new File(new File(Environment.getDataSystemDirectory(), "appops"), 1976 "wtf" + TimeUtils.formatForLogging(System.currentTimeMillis())); 1977 if (file.createNewFile()) { 1978 try (PrintWriter writer = new PrintWriter(file)) { 1979 if (t != null) { 1980 writer.append('\n').append(t.toString()); 1981 } 1982 writer.append('\n').append(Debug.getCallers(10)); 1983 if (storage != null) { 1984 writer.append("\nfiles: " + Arrays.toString(storage.listFiles())); 1985 } else { 1986 writer.append("\nfiles: none"); 1987 } 1988 } 1989 } 1990 } catch (IOException e) { 1991 /* ignore */ 1992 } 1993 } 1994 } 1995 } 1996