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