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