1 /* 2 * Copyright (C) 2021 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 17 package com.android.server.appop; 18 19 import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; 20 import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; 21 import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; 22 import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; 23 import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; 24 import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; 25 import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; 26 import static android.app.AppOpsManager.FILTER_BY_UID; 27 import static android.app.AppOpsManager.OP_FLAGS_ALL; 28 import static android.app.AppOpsManager.OP_NONE; 29 import static android.app.AppOpsManager.flagsToString; 30 import static android.app.AppOpsManager.getUidStateName; 31 import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; 32 33 import static java.lang.Math.max; 34 35 import android.annotation.NonNull; 36 import android.annotation.Nullable; 37 import android.app.AppOpsManager; 38 import android.os.Environment; 39 import android.os.FileUtils; 40 import android.permission.flags.Flags; 41 import android.util.ArrayMap; 42 import android.util.AtomicFile; 43 import android.util.Slog; 44 import android.util.Xml; 45 46 import com.android.internal.annotations.GuardedBy; 47 import com.android.internal.util.ArrayUtils; 48 import com.android.internal.util.XmlUtils; 49 import com.android.modules.utils.TypedXmlPullParser; 50 import com.android.modules.utils.TypedXmlSerializer; 51 52 import java.io.File; 53 import java.io.FileInputStream; 54 import java.io.FileNotFoundException; 55 import java.io.FileOutputStream; 56 import java.io.IOException; 57 import java.io.PrintWriter; 58 import java.text.SimpleDateFormat; 59 import java.time.Instant; 60 import java.time.temporal.ChronoUnit; 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.Comparator; 64 import java.util.Date; 65 import java.util.List; 66 import java.util.Objects; 67 import java.util.Set; 68 69 /** 70 * Xml persistence implementation for discrete ops. 71 * 72 * <p> 73 * Every time state is saved (default is 30 minutes), memory state is dumped to a 74 * new file and memory state is cleared. Files older than time limit are deleted 75 * during the process. 76 * <p> 77 * When request comes in, files are read and requested information is collected 78 * and delivered. Information is cached in memory until the next state save (up to 30 minutes), to 79 * avoid reading disk if more API calls come in a quick succession. 80 * <p> 81 * THREADING AND LOCKING: 82 * For in-memory transactions this class relies on {@link DiscreteOpsXmlRegistry#mInMemoryLock}. 83 * It is assumed that the same lock is used for in-memory transactions in {@link AppOpsService}, 84 * {@link LegacyHistoricalRegistry}, and {@link DiscreteOpsXmlRegistry }. 85 * {@link DiscreteOpsRegistry#recordDiscreteAccess} must only be called while holding this lock. 86 * {@link DiscreteOpsXmlRegistry#mOnDiskLock} is used when disk transactions are performed. 87 * It is very important to release {@link DiscreteOpsXmlRegistry#mInMemoryLock} as soon as 88 * possible, as no AppOps related transactions across the system can be performed while it is held. 89 * 90 */ 91 class DiscreteOpsXmlRegistry extends DiscreteOpsRegistry { 92 static final String DISCRETE_HISTORY_FILE_SUFFIX = "tl"; 93 private static final String TAG = DiscreteOpsXmlRegistry.class.getSimpleName(); 94 95 private static final String TAG_HISTORY = "h"; 96 private static final String ATTR_VERSION = "v"; 97 private static final String ATTR_LARGEST_CHAIN_ID = "lc"; 98 private static final int CURRENT_VERSION = 1; 99 100 private static final String TAG_UID = "u"; 101 private static final String ATTR_UID = "ui"; 102 103 private static final String TAG_PACKAGE = "p"; 104 private static final String ATTR_PACKAGE_NAME = "pn"; 105 106 private static final String TAG_OP = "o"; 107 private static final String ATTR_OP_ID = "op"; 108 109 private static final String ATTR_DEVICE_ID = "di"; 110 111 private static final String TAG_TAG = "a"; 112 private static final String ATTR_TAG = "at"; 113 114 private static final String TAG_ENTRY = "e"; 115 private static final String ATTR_NOTE_TIME = "nt"; 116 private static final String ATTR_NOTE_DURATION = "nd"; 117 private static final String ATTR_UID_STATE = "us"; 118 private static final String ATTR_FLAGS = "f"; 119 private static final String ATTR_ATTRIBUTION_FLAGS = "af"; 120 private static final String ATTR_CHAIN_ID = "ci"; 121 122 // Lock for read/write access to on disk state 123 private final Object mOnDiskLock = new Object(); 124 125 //Lock for read/write access to in memory state 126 private final @NonNull Object mInMemoryLock; 127 128 @GuardedBy("mOnDiskLock") 129 private File mDiscreteAccessDir; 130 131 @GuardedBy("mInMemoryLock") 132 private DiscreteOps mDiscreteOps; 133 134 @GuardedBy("mOnDiskLock") 135 private DiscreteOps mCachedOps = null; 136 DiscreteOpsXmlRegistry(Object inMemoryLock)137 DiscreteOpsXmlRegistry(Object inMemoryLock) { 138 this(inMemoryLock, getDiscreteOpsDir()); 139 } 140 141 // constructor for tests. DiscreteOpsXmlRegistry(Object inMemoryLock, File discreteAccessDir)142 DiscreteOpsXmlRegistry(Object inMemoryLock, File discreteAccessDir) { 143 mInMemoryLock = inMemoryLock; 144 synchronized (mOnDiskLock) { 145 mDiscreteAccessDir = discreteAccessDir; 146 createDiscreteAccessDirLocked(); 147 int largestChainId = readLargestChainIdFromDiskLocked(); 148 synchronized (mInMemoryLock) { 149 mDiscreteOps = new DiscreteOps(largestChainId); 150 } 151 } 152 } 153 getDiscreteOpsDir()154 static File getDiscreteOpsDir() { 155 return new File(new File(Environment.getDataSystemDirectory(), "appops"), "discrete"); 156 } 157 recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)158 void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, 159 @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, 160 @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, 161 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { 162 if (!isDiscreteOp(op, flags)) { 163 return; 164 } 165 synchronized (mInMemoryLock) { 166 if (Flags.deviceAwareAppOpNewSchemaEnabled()) { 167 mDiscreteOps.addDiscreteAccess(op, uid, packageName, deviceId, attributionTag, 168 flags, uidState, accessTime, accessDuration, attributionFlags, 169 attributionChainId); 170 } else { 171 mDiscreteOps.addDiscreteAccess(op, uid, packageName, PERSISTENT_DEVICE_ID_DEFAULT, 172 attributionTag, flags, uidState, accessTime, accessDuration, 173 attributionFlags, attributionChainId); 174 } 175 176 } 177 } 178 writeAndClearOldAccessHistory()179 void writeAndClearOldAccessHistory() { 180 synchronized (mOnDiskLock) { 181 if (mDiscreteAccessDir == null) { 182 Slog.d(TAG, "State not saved - persistence not initialized."); 183 return; 184 } 185 DiscreteOps discreteOps; 186 synchronized (mInMemoryLock) { 187 discreteOps = mDiscreteOps; 188 mDiscreteOps = new DiscreteOps(discreteOps.mChainIdOffset); 189 mCachedOps = null; 190 } 191 deleteOldDiscreteHistoryFilesLocked(); 192 if (!discreteOps.isEmpty()) { 193 persistDiscreteOpsLocked(discreteOps); 194 } 195 } 196 } 197 migrateSqliteData(DiscreteOps sqliteOps)198 void migrateSqliteData(DiscreteOps sqliteOps) { 199 synchronized (mOnDiskLock) { 200 if (mDiscreteAccessDir == null) { 201 Slog.d(TAG, "State not saved - persistence not initialized."); 202 return; 203 } 204 synchronized (mInMemoryLock) { 205 mDiscreteOps.mLargestChainId = sqliteOps.mLargestChainId; 206 mDiscreteOps.mChainIdOffset = sqliteOps.mChainIdOffset; 207 } 208 if (!sqliteOps.isEmpty()) { 209 persistDiscreteOpsLocked(sqliteOps); 210 } 211 } 212 } 213 addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, long beginTimeMillis, long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, Set<String> attributionExemptPkgs)214 void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, 215 long beginTimeMillis, long endTimeMillis, 216 @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, 217 @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, 218 @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, 219 Set<String> attributionExemptPkgs) { 220 boolean assembleChains = attributionExemptPkgs != null; 221 DiscreteOps discreteOps = getAllDiscreteOps(); 222 ArrayMap<Integer, AttributionChain> attributionChains = new ArrayMap<>(); 223 if (assembleChains) { 224 attributionChains = createAttributionChains(discreteOps, attributionExemptPkgs); 225 } 226 beginTimeMillis = max(beginTimeMillis, Instant.now().minus(sDiscreteHistoryCutoff, 227 ChronoUnit.MILLIS).toEpochMilli()); 228 discreteOps.filter(beginTimeMillis, endTimeMillis, filter, uidFilter, packageNameFilter, 229 opNamesFilter, attributionTagFilter, flagsFilter, attributionChains); 230 discreteOps.applyToHistoricalOps(result, attributionChains); 231 } 232 readLargestChainIdFromDiskLocked()233 int readLargestChainIdFromDiskLocked() { 234 final File[] files = mDiscreteAccessDir.listFiles(); 235 if (files != null && files.length > 0) { 236 File latestFile = null; 237 long latestFileTimestamp = 0; 238 for (File f : files) { 239 final String fileName = f.getName(); 240 if (!fileName.endsWith(DISCRETE_HISTORY_FILE_SUFFIX)) { 241 continue; 242 } 243 long timestamp = Long.valueOf(fileName.substring(0, 244 fileName.length() - DISCRETE_HISTORY_FILE_SUFFIX.length())); 245 if (latestFileTimestamp < timestamp) { 246 latestFile = f; 247 latestFileTimestamp = timestamp; 248 } 249 } 250 if (latestFile == null) { 251 return 0; 252 } 253 FileInputStream stream; 254 try { 255 stream = new FileInputStream(latestFile); 256 } catch (FileNotFoundException e) { 257 return 0; 258 } 259 try { 260 TypedXmlPullParser parser = Xml.resolvePullParser(stream); 261 XmlUtils.beginDocument(parser, TAG_HISTORY); 262 263 final int largestChainId = parser.getAttributeInt(null, ATTR_LARGEST_CHAIN_ID, 0); 264 return largestChainId; 265 } catch (Throwable t) { 266 return 0; 267 } finally { 268 try { 269 stream.close(); 270 } catch (IOException e) { 271 } 272 } 273 } else { 274 return 0; 275 } 276 } 277 createAttributionChains( DiscreteOps discreteOps, Set<String> attributionExemptPkgs)278 private ArrayMap<Integer, AttributionChain> createAttributionChains( 279 DiscreteOps discreteOps, Set<String> attributionExemptPkgs) { 280 ArrayMap<Integer, AttributionChain> chains = new ArrayMap<>(); 281 int nUids = discreteOps.mUids.size(); 282 for (int uidNum = 0; uidNum < nUids; uidNum++) { 283 ArrayMap<String, DiscretePackageOps> pkgs = discreteOps.mUids.valueAt(uidNum).mPackages; 284 int uid = discreteOps.mUids.keyAt(uidNum); 285 int nPackages = pkgs.size(); 286 for (int pkgNum = 0; pkgNum < nPackages; pkgNum++) { 287 ArrayMap<Integer, DiscreteOp> ops = pkgs.valueAt(pkgNum).mPackageOps; 288 String pkg = pkgs.keyAt(pkgNum); 289 int nOps = ops.size(); 290 for (int opNum = 0; opNum < nOps; opNum++) { 291 int op = ops.keyAt(opNum); 292 ArrayMap<String, DiscreteDeviceOp> deviceOps = 293 ops.valueAt(opNum).mDeviceAttributedOps; 294 295 int nDeviceOps = deviceOps.size(); 296 for (int deviceNum = 0; deviceNum < nDeviceOps; deviceNum++) { 297 ArrayMap<String, List<DiscreteOpEvent>> attrOps = 298 deviceOps.valueAt(deviceNum).mAttributedOps; 299 300 int nAttrOps = attrOps.size(); 301 for (int attrOpNum = 0; attrOpNum < nAttrOps; attrOpNum++) { 302 List<DiscreteOpEvent> opEvents = attrOps.valueAt(attrOpNum); 303 String attributionTag = attrOps.keyAt(attrOpNum); 304 int nOpEvents = opEvents.size(); 305 for (int opEventNum = 0; opEventNum < nOpEvents; opEventNum++) { 306 DiscreteOpEvent event = opEvents.get(opEventNum); 307 if (event == null 308 || event.mAttributionChainId == ATTRIBUTION_CHAIN_ID_NONE 309 || (event.mAttributionFlags & ATTRIBUTION_FLAG_TRUSTED) 310 == 0) { 311 continue; 312 } 313 314 if (!chains.containsKey(event.mAttributionChainId)) { 315 chains.put(event.mAttributionChainId, 316 new AttributionChain(attributionExemptPkgs)); 317 } 318 chains.get(event.mAttributionChainId) 319 .addEvent(pkg, uid, attributionTag, op, event); 320 } 321 } 322 } 323 } 324 } 325 } 326 return chains; 327 } 328 readDiscreteOpsFromDisk(DiscreteOps discreteOps)329 private void readDiscreteOpsFromDisk(DiscreteOps discreteOps) { 330 synchronized (mOnDiskLock) { 331 long beginTimeMillis = Instant.now().minus(sDiscreteHistoryCutoff, 332 ChronoUnit.MILLIS).toEpochMilli(); 333 334 final File[] files = mDiscreteAccessDir.listFiles(); 335 if (files != null && files.length > 0) { 336 for (File f : files) { 337 final String fileName = f.getName(); 338 if (!fileName.endsWith(DISCRETE_HISTORY_FILE_SUFFIX)) { 339 continue; 340 } 341 long timestamp = Long.valueOf(fileName.substring(0, 342 fileName.length() - DISCRETE_HISTORY_FILE_SUFFIX.length())); 343 if (timestamp < beginTimeMillis) { 344 continue; 345 } 346 discreteOps.readFromFile(f, beginTimeMillis); 347 } 348 } 349 } 350 } 351 clearHistory()352 void clearHistory() { 353 synchronized (mOnDiskLock) { 354 synchronized (mInMemoryLock) { 355 mDiscreteOps = new DiscreteOps(0); 356 } 357 clearOnDiskHistoryLocked(); 358 } 359 } 360 deleteDiscreteOpsDir()361 void deleteDiscreteOpsDir() { 362 synchronized (mOnDiskLock) { 363 mCachedOps = null; 364 FileUtils.deleteContentsAndDir(mDiscreteAccessDir); 365 } 366 } 367 clearHistory(int uid, String packageName)368 void clearHistory(int uid, String packageName) { 369 synchronized (mOnDiskLock) { 370 DiscreteOps discreteOps; 371 synchronized (mInMemoryLock) { 372 discreteOps = getAllDiscreteOps(); 373 clearHistory(); 374 } 375 discreteOps.clearHistory(uid, packageName); 376 persistDiscreteOpsLocked(discreteOps); 377 } 378 } 379 offsetHistory(long offset)380 void offsetHistory(long offset) { 381 synchronized (mOnDiskLock) { 382 DiscreteOps discreteOps; 383 synchronized (mInMemoryLock) { 384 discreteOps = getAllDiscreteOps(); 385 clearHistory(); 386 } 387 discreteOps.offsetHistory(offset); 388 persistDiscreteOpsLocked(discreteOps); 389 } 390 } 391 dump(@onNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, @Nullable String attributionTagFilter, @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)392 void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, 393 @Nullable String attributionTagFilter, 394 @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, 395 @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, 396 int nDiscreteOps) { 397 DiscreteOps discreteOps = getAllDiscreteOps(); 398 String[] opNamesFilter = dumpOp == OP_NONE ? null 399 : new String[]{AppOpsManager.opToPublicName(dumpOp)}; 400 discreteOps.filter(0, Instant.now().toEpochMilli(), filter, uidFilter, packageNameFilter, 401 opNamesFilter, attributionTagFilter, OP_FLAGS_ALL, new ArrayMap<>()); 402 pw.print(prefix); 403 pw.print("Largest chain id: "); 404 pw.print(mDiscreteOps.mLargestChainId); 405 pw.println(); 406 discreteOps.dump(pw, sdf, date, prefix, nDiscreteOps); 407 } 408 clearOnDiskHistoryLocked()409 private void clearOnDiskHistoryLocked() { 410 mCachedOps = null; 411 FileUtils.deleteContentsAndDir(mDiscreteAccessDir); 412 createDiscreteAccessDir(); 413 } 414 getAllDiscreteOps()415 DiscreteOps getAllDiscreteOps() { 416 DiscreteOps discreteOps = new DiscreteOps(0); 417 418 synchronized (mOnDiskLock) { 419 synchronized (mInMemoryLock) { 420 discreteOps.merge(mDiscreteOps); 421 } 422 if (mCachedOps == null) { 423 mCachedOps = new DiscreteOps(0); 424 readDiscreteOpsFromDisk(mCachedOps); 425 } 426 discreteOps.merge(mCachedOps); 427 return discreteOps; 428 } 429 } 430 431 /** 432 * Represents a chain of usages, each attributing its usage to the one before it 433 */ 434 private static final class AttributionChain { 435 private static final class OpEvent { 436 String mPkgName; 437 int mUid; 438 String mAttributionTag; 439 int mOpCode; 440 DiscreteOpEvent mOpEvent; 441 OpEvent(String pkgName, int uid, String attributionTag, int opCode, DiscreteOpEvent event)442 OpEvent(String pkgName, int uid, String attributionTag, int opCode, 443 DiscreteOpEvent event) { 444 mPkgName = pkgName; 445 mUid = uid; 446 mAttributionTag = attributionTag; 447 mOpCode = opCode; 448 mOpEvent = event; 449 } 450 matches(String pkgName, int uid, String attributionTag, int opCode, DiscreteOpEvent event)451 public boolean matches(String pkgName, int uid, String attributionTag, int opCode, 452 DiscreteOpEvent event) { 453 return Objects.equals(pkgName, mPkgName) && mUid == uid 454 && Objects.equals(attributionTag, mAttributionTag) && mOpCode == opCode 455 && mOpEvent.mAttributionChainId == event.mAttributionChainId 456 && mOpEvent.mAttributionFlags == event.mAttributionFlags 457 && mOpEvent.mNoteTime == event.mNoteTime; 458 } 459 packageOpEquals(OpEvent other)460 public boolean packageOpEquals(OpEvent other) { 461 return Objects.equals(other.mPkgName, mPkgName) && other.mUid == mUid 462 && Objects.equals(other.mAttributionTag, mAttributionTag) 463 && mOpCode == other.mOpCode; 464 } 465 equalsExceptDuration(OpEvent other)466 public boolean equalsExceptDuration(OpEvent other) { 467 if (other.mOpEvent.mNoteDuration == mOpEvent.mNoteDuration) { 468 return false; 469 } 470 return packageOpEquals(other) && mOpEvent.equalsExceptDuration(other.mOpEvent); 471 } 472 } 473 474 ArrayList<OpEvent> mChain = new ArrayList<>(); 475 Set<String> mExemptPkgs; 476 OpEvent mStartEvent = null; 477 OpEvent mLastVisibleEvent = null; 478 AttributionChain(Set<String> exemptPkgs)479 AttributionChain(Set<String> exemptPkgs) { 480 mExemptPkgs = exemptPkgs; 481 } 482 isComplete()483 boolean isComplete() { 484 return !mChain.isEmpty() && getStart() != null && isEnd(mChain.get(mChain.size() - 1)); 485 } 486 isStart(String pkgName, int uid, String attributionTag, int op, DiscreteOpEvent opEvent)487 boolean isStart(String pkgName, int uid, String attributionTag, int op, 488 DiscreteOpEvent opEvent) { 489 if (mStartEvent == null || opEvent == null) { 490 return false; 491 } 492 return mStartEvent.matches(pkgName, uid, attributionTag, op, opEvent); 493 } 494 getStart()495 private OpEvent getStart() { 496 return mChain.isEmpty() || !isStart(mChain.get(0)) ? null : mChain.get(0); 497 } 498 getLastVisible()499 private OpEvent getLastVisible() { 500 // Search all nodes but the first one, which is the start node 501 for (int i = mChain.size() - 1; i > 0; i--) { 502 OpEvent event = mChain.get(i); 503 if (!mExemptPkgs.contains(event.mPkgName)) { 504 return event; 505 } 506 } 507 return null; 508 } 509 addEvent(String pkgName, int uid, String attributionTag, int op, DiscreteOpEvent opEvent)510 void addEvent(String pkgName, int uid, String attributionTag, int op, 511 DiscreteOpEvent opEvent) { 512 OpEvent event = new OpEvent(pkgName, uid, attributionTag, op, opEvent); 513 514 // check if we have a matching event, without duration, replacing duration otherwise 515 for (int i = 0; i < mChain.size(); i++) { 516 OpEvent item = mChain.get(i); 517 if (item.equalsExceptDuration(event)) { 518 if (event.mOpEvent.mNoteDuration != -1) { 519 item.mOpEvent = event.mOpEvent; 520 } 521 return; 522 } 523 } 524 525 if (mChain.isEmpty() || isEnd(event)) { 526 mChain.add(event); 527 } else if (isStart(event)) { 528 mChain.add(0, event); 529 530 } else { 531 for (int i = 0; i < mChain.size(); i++) { 532 OpEvent currEvent = mChain.get(i); 533 if ((!isStart(currEvent) 534 && currEvent.mOpEvent.mNoteTime > event.mOpEvent.mNoteTime) 535 || i == mChain.size() - 1 && isEnd(currEvent)) { 536 mChain.add(i, event); 537 break; 538 } else if (i == mChain.size() - 1) { 539 mChain.add(event); 540 break; 541 } 542 } 543 } 544 mStartEvent = isComplete() ? getStart() : null; 545 mLastVisibleEvent = isComplete() ? getLastVisible() : null; 546 } 547 isEnd(OpEvent event)548 private boolean isEnd(OpEvent event) { 549 return event != null 550 && (event.mOpEvent.mAttributionFlags & ATTRIBUTION_FLAG_ACCESSOR) != 0; 551 } 552 isStart(OpEvent event)553 private boolean isStart(OpEvent event) { 554 return event != null 555 && (event.mOpEvent.mAttributionFlags & ATTRIBUTION_FLAG_RECEIVER) != 0; 556 } 557 } 558 559 static final class DiscreteOps { 560 ArrayMap<Integer, DiscreteUidOps> mUids; 561 int mChainIdOffset; 562 int mLargestChainId; 563 DiscreteOps(int chainIdOffset)564 DiscreteOps(int chainIdOffset) { 565 mUids = new ArrayMap<>(); 566 mChainIdOffset = chainIdOffset; 567 mLargestChainId = chainIdOffset; 568 } 569 isEmpty()570 boolean isEmpty() { 571 return mUids.isEmpty(); 572 } 573 merge(DiscreteOps other)574 void merge(DiscreteOps other) { 575 mLargestChainId = max(mLargestChainId, other.mLargestChainId); 576 int nUids = other.mUids.size(); 577 for (int i = 0; i < nUids; i++) { 578 int uid = other.mUids.keyAt(i); 579 DiscreteUidOps uidOps = other.mUids.valueAt(i); 580 getOrCreateDiscreteUidOps(uid).merge(uidOps); 581 } 582 } 583 addDiscreteAccess(int op, int uid, @NonNull String packageName, @NonNull String deviceId, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)584 void addDiscreteAccess(int op, int uid, @NonNull String packageName, 585 @NonNull String deviceId, @Nullable String attributionTag, 586 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, 587 long accessTime, long accessDuration, 588 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { 589 int offsetChainId = attributionChainId; 590 if (attributionChainId != ATTRIBUTION_CHAIN_ID_NONE) { 591 offsetChainId = attributionChainId + mChainIdOffset; 592 if (offsetChainId > mLargestChainId) { 593 mLargestChainId = offsetChainId; 594 } else if (offsetChainId < 0) { 595 // handle overflow 596 offsetChainId = 0; 597 mLargestChainId = 0; 598 mChainIdOffset = -1 * attributionChainId; 599 } 600 } 601 getOrCreateDiscreteUidOps(uid).addDiscreteAccess(op, packageName, deviceId, 602 attributionTag, flags, uidState, accessTime, accessDuration, attributionFlags, 603 offsetChainId); 604 } 605 filter(long beginTimeMillis, long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, ArrayMap<Integer, AttributionChain> attributionChains)606 private void filter(long beginTimeMillis, long endTimeMillis, 607 @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, 608 @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, 609 @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, 610 ArrayMap<Integer, AttributionChain> attributionChains) { 611 if ((filter & FILTER_BY_UID) != 0) { 612 ArrayMap<Integer, DiscreteUidOps> uids = new ArrayMap<>(); 613 uids.put(uidFilter, getOrCreateDiscreteUidOps(uidFilter)); 614 mUids = uids; 615 } 616 int nUids = mUids.size(); 617 for (int i = nUids - 1; i >= 0; i--) { 618 mUids.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, packageNameFilter, 619 opNamesFilter, attributionTagFilter, flagsFilter, mUids.keyAt(i), 620 attributionChains); 621 if (mUids.valueAt(i).isEmpty()) { 622 mUids.removeAt(i); 623 } 624 } 625 } 626 offsetHistory(long offset)627 private void offsetHistory(long offset) { 628 int nUids = mUids.size(); 629 for (int i = 0; i < nUids; i++) { 630 mUids.valueAt(i).offsetHistory(offset); 631 } 632 } 633 clearHistory(int uid, String packageName)634 private void clearHistory(int uid, String packageName) { 635 if (mUids.containsKey(uid)) { 636 mUids.get(uid).clearPackage(packageName); 637 if (mUids.get(uid).isEmpty()) { 638 mUids.remove(uid); 639 } 640 } 641 } 642 applyToHistoricalOps(AppOpsManager.HistoricalOps result, ArrayMap<Integer, AttributionChain> attributionChains)643 private void applyToHistoricalOps(AppOpsManager.HistoricalOps result, 644 ArrayMap<Integer, AttributionChain> attributionChains) { 645 int nUids = mUids.size(); 646 for (int i = 0; i < nUids; i++) { 647 mUids.valueAt(i).applyToHistory(result, mUids.keyAt(i), attributionChains); 648 } 649 } 650 writeToStream(FileOutputStream stream)651 private void writeToStream(FileOutputStream stream) throws Exception { 652 TypedXmlSerializer out = Xml.resolveSerializer(stream); 653 654 out.startDocument(null, true); 655 out.startTag(null, TAG_HISTORY); 656 out.attributeInt(null, ATTR_VERSION, CURRENT_VERSION); 657 out.attributeInt(null, ATTR_LARGEST_CHAIN_ID, mLargestChainId); 658 659 int nUids = mUids.size(); 660 for (int i = 0; i < nUids; i++) { 661 out.startTag(null, TAG_UID); 662 out.attributeInt(null, ATTR_UID, mUids.keyAt(i)); 663 mUids.valueAt(i).serialize(out); 664 out.endTag(null, TAG_UID); 665 } 666 out.endTag(null, TAG_HISTORY); 667 out.endDocument(); 668 } 669 dump(@onNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)670 private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf, 671 @NonNull Date date, @NonNull String prefix, int nDiscreteOps) { 672 int nUids = mUids.size(); 673 for (int i = 0; i < nUids; i++) { 674 pw.print(prefix); 675 pw.print("Uid: "); 676 pw.print(mUids.keyAt(i)); 677 pw.println(); 678 mUids.valueAt(i).dump(pw, sdf, date, prefix + " ", nDiscreteOps); 679 } 680 } 681 getOrCreateDiscreteUidOps(int uid)682 private DiscreteUidOps getOrCreateDiscreteUidOps(int uid) { 683 DiscreteUidOps result = mUids.get(uid); 684 if (result == null) { 685 result = new DiscreteUidOps(); 686 mUids.put(uid, result); 687 } 688 return result; 689 } 690 readFromFile(File f, long beginTimeMillis)691 private void readFromFile(File f, long beginTimeMillis) { 692 FileInputStream stream; 693 try { 694 stream = new FileInputStream(f); 695 } catch (FileNotFoundException e) { 696 return; 697 } 698 try { 699 TypedXmlPullParser parser = Xml.resolvePullParser(stream); 700 XmlUtils.beginDocument(parser, TAG_HISTORY); 701 702 // We haven't released version 1 and have more detailed 703 // accounting - just nuke the current state 704 final int version = parser.getAttributeInt(null, ATTR_VERSION); 705 if (version != CURRENT_VERSION) { 706 throw new IllegalStateException("Dropping unsupported discrete history " + f); 707 } 708 int depth = parser.getDepth(); 709 while (XmlUtils.nextElementWithin(parser, depth)) { 710 if (TAG_UID.equals(parser.getName())) { 711 int uid = parser.getAttributeInt(null, ATTR_UID, -1); 712 getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis); 713 } 714 } 715 } catch (Throwable t) { 716 Slog.e(TAG, "Failed to read file " + f.getName() + " " + t.getMessage() + " " 717 + Arrays.toString(t.getStackTrace())); 718 } finally { 719 try { 720 stream.close(); 721 } catch (IOException e) { 722 } 723 } 724 } 725 } 726 createDiscreteAccessDir()727 private void createDiscreteAccessDir() { 728 if (!mDiscreteAccessDir.exists()) { 729 if (!mDiscreteAccessDir.mkdirs()) { 730 Slog.e(TAG, "Failed to create DiscreteRegistry directory"); 731 } 732 FileUtils.setPermissions(mDiscreteAccessDir.getPath(), 733 FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1); 734 } 735 } 736 persistDiscreteOpsLocked(DiscreteOps discreteOps)737 private void persistDiscreteOpsLocked(DiscreteOps discreteOps) { 738 long currentTimeStamp = Instant.now().toEpochMilli(); 739 final AtomicFile file = new AtomicFile(new File(mDiscreteAccessDir, 740 currentTimeStamp + DISCRETE_HISTORY_FILE_SUFFIX)); 741 FileOutputStream stream = null; 742 try { 743 stream = file.startWrite(); 744 discreteOps.writeToStream(stream); 745 file.finishWrite(stream); 746 } catch (Throwable t) { 747 Slog.e(TAG, 748 "Error writing timeline state: " + t.getMessage() + " " 749 + Arrays.toString(t.getStackTrace())); 750 if (stream != null) { 751 file.failWrite(stream); 752 } 753 } 754 } 755 deleteOldDiscreteHistoryFilesLocked()756 private void deleteOldDiscreteHistoryFilesLocked() { 757 final File[] files = mDiscreteAccessDir.listFiles(); 758 if (files != null && files.length > 0) { 759 for (File f : files) { 760 final String fileName = f.getName(); 761 if (!fileName.endsWith(DISCRETE_HISTORY_FILE_SUFFIX)) { 762 continue; 763 } 764 try { 765 long timestamp = Long.valueOf(fileName.substring(0, 766 fileName.length() - DISCRETE_HISTORY_FILE_SUFFIX.length())); 767 if (Instant.now().minus(sDiscreteHistoryCutoff, 768 ChronoUnit.MILLIS).toEpochMilli() > timestamp) { 769 f.delete(); 770 Slog.e(TAG, "Deleting file " + fileName); 771 772 } 773 } catch (Throwable t) { 774 Slog.e(TAG, "Error while cleaning timeline files: ", t); 775 } 776 } 777 } 778 } 779 createDiscreteAccessDirLocked()780 private void createDiscreteAccessDirLocked() { 781 if (!mDiscreteAccessDir.exists()) { 782 if (!mDiscreteAccessDir.mkdirs()) { 783 Slog.e(TAG, "Failed to create DiscreteRegistry directory"); 784 } 785 FileUtils.setPermissions(mDiscreteAccessDir.getPath(), 786 FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1); 787 } 788 } 789 790 static final class DiscreteUidOps { 791 ArrayMap<String, DiscretePackageOps> mPackages; 792 DiscreteUidOps()793 DiscreteUidOps() { 794 mPackages = new ArrayMap<>(); 795 } 796 isEmpty()797 boolean isEmpty() { 798 return mPackages.isEmpty(); 799 } 800 merge(DiscreteUidOps other)801 void merge(DiscreteUidOps other) { 802 int nPackages = other.mPackages.size(); 803 for (int i = 0; i < nPackages; i++) { 804 String packageName = other.mPackages.keyAt(i); 805 DiscretePackageOps p = other.mPackages.valueAt(i); 806 getOrCreateDiscretePackageOps(packageName).merge(p); 807 } 808 } 809 filter(long beginTimeMillis, long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, int currentUid, ArrayMap<Integer, AttributionChain> attributionChains)810 private void filter(long beginTimeMillis, long endTimeMillis, 811 @AppOpsManager.HistoricalOpsRequestFilter int filter, 812 @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, 813 @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, 814 int currentUid, ArrayMap<Integer, AttributionChain> attributionChains) { 815 if ((filter & FILTER_BY_PACKAGE_NAME) != 0) { 816 ArrayMap<String, DiscretePackageOps> packages = new ArrayMap<>(); 817 packages.put(packageNameFilter, getOrCreateDiscretePackageOps(packageNameFilter)); 818 mPackages = packages; 819 } 820 int nPackages = mPackages.size(); 821 for (int i = nPackages - 1; i >= 0; i--) { 822 mPackages.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, opNamesFilter, 823 attributionTagFilter, flagsFilter, currentUid, mPackages.keyAt(i), 824 attributionChains); 825 if (mPackages.valueAt(i).isEmpty()) { 826 mPackages.removeAt(i); 827 } 828 } 829 } 830 offsetHistory(long offset)831 private void offsetHistory(long offset) { 832 int nPackages = mPackages.size(); 833 for (int i = 0; i < nPackages; i++) { 834 mPackages.valueAt(i).offsetHistory(offset); 835 } 836 } 837 clearPackage(String packageName)838 private void clearPackage(String packageName) { 839 mPackages.remove(packageName); 840 } 841 addDiscreteAccess(int op, @NonNull String packageName, @NonNull String deviceId, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)842 void addDiscreteAccess(int op, @NonNull String packageName, @NonNull String deviceId, 843 @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, 844 @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, 845 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { 846 getOrCreateDiscretePackageOps(packageName).addDiscreteAccess(op, deviceId, 847 attributionTag, flags, uidState, accessTime, accessDuration, 848 attributionFlags, attributionChainId); 849 } 850 getOrCreateDiscretePackageOps(String packageName)851 private DiscretePackageOps getOrCreateDiscretePackageOps(String packageName) { 852 DiscretePackageOps result = mPackages.get(packageName); 853 if (result == null) { 854 result = new DiscretePackageOps(); 855 mPackages.put(packageName, result); 856 } 857 return result; 858 } 859 applyToHistory(AppOpsManager.HistoricalOps result, int uid, @NonNull ArrayMap<Integer, AttributionChain> attributionChains)860 private void applyToHistory(AppOpsManager.HistoricalOps result, int uid, 861 @NonNull ArrayMap<Integer, AttributionChain> attributionChains) { 862 int nPackages = mPackages.size(); 863 for (int i = 0; i < nPackages; i++) { 864 mPackages.valueAt(i).applyToHistory(result, uid, mPackages.keyAt(i), 865 attributionChains); 866 } 867 } 868 serialize(TypedXmlSerializer out)869 void serialize(TypedXmlSerializer out) throws Exception { 870 int nPackages = mPackages.size(); 871 for (int i = 0; i < nPackages; i++) { 872 out.startTag(null, TAG_PACKAGE); 873 out.attribute(null, ATTR_PACKAGE_NAME, mPackages.keyAt(i)); 874 mPackages.valueAt(i).serialize(out); 875 out.endTag(null, TAG_PACKAGE); 876 } 877 } 878 dump(@onNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)879 private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf, 880 @NonNull Date date, @NonNull String prefix, int nDiscreteOps) { 881 int nPackages = mPackages.size(); 882 for (int i = 0; i < nPackages; i++) { 883 pw.print(prefix); 884 pw.print("Package: "); 885 pw.print(mPackages.keyAt(i)); 886 pw.println(); 887 mPackages.valueAt(i).dump(pw, sdf, date, prefix + " ", nDiscreteOps); 888 } 889 } 890 deserialize(TypedXmlPullParser parser, long beginTimeMillis)891 void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception { 892 int depth = parser.getDepth(); 893 while (XmlUtils.nextElementWithin(parser, depth)) { 894 if (TAG_PACKAGE.equals(parser.getName())) { 895 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); 896 getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis); 897 } 898 } 899 } 900 } 901 902 static final class DiscretePackageOps { 903 ArrayMap<Integer, DiscreteOp> mPackageOps; 904 DiscretePackageOps()905 DiscretePackageOps() { 906 mPackageOps = new ArrayMap<>(); 907 } 908 isEmpty()909 boolean isEmpty() { 910 return mPackageOps.isEmpty(); 911 } 912 addDiscreteAccess(int op, @NonNull String deviceId, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)913 void addDiscreteAccess(int op, @NonNull String deviceId, @Nullable String attributionTag, 914 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, 915 long accessTime, long accessDuration, 916 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { 917 getOrCreateDiscreteOp(op).addDiscreteAccess(deviceId, attributionTag, flags, uidState, 918 accessTime, accessDuration, attributionFlags, attributionChainId); 919 } 920 merge(DiscretePackageOps other)921 void merge(DiscretePackageOps other) { 922 int nOps = other.mPackageOps.size(); 923 for (int i = 0; i < nOps; i++) { 924 int opId = other.mPackageOps.keyAt(i); 925 DiscreteOp op = other.mPackageOps.valueAt(i); 926 getOrCreateDiscreteOp(opId).merge(op); 927 } 928 } 929 filter(long beginTimeMillis, long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, int currentUid, String currentPkgName, ArrayMap<Integer, AttributionChain> attributionChains)930 private void filter(long beginTimeMillis, long endTimeMillis, 931 @AppOpsManager.HistoricalOpsRequestFilter int filter, 932 @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, 933 @AppOpsManager.OpFlags int flagsFilter, int currentUid, String currentPkgName, 934 ArrayMap<Integer, AttributionChain> attributionChains) { 935 int nOps = mPackageOps.size(); 936 for (int i = nOps - 1; i >= 0; i--) { 937 int opId = mPackageOps.keyAt(i); 938 if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter, 939 AppOpsManager.opToPublicName(opId))) { 940 mPackageOps.removeAt(i); 941 continue; 942 } 943 mPackageOps.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, 944 attributionTagFilter, flagsFilter, currentUid, currentPkgName, 945 mPackageOps.keyAt(i), attributionChains); 946 if (mPackageOps.valueAt(i).isEmpty()) { 947 mPackageOps.removeAt(i); 948 } 949 } 950 } 951 offsetHistory(long offset)952 private void offsetHistory(long offset) { 953 int nOps = mPackageOps.size(); 954 for (int i = 0; i < nOps; i++) { 955 mPackageOps.valueAt(i).offsetHistory(offset); 956 } 957 } 958 getOrCreateDiscreteOp(int op)959 private DiscreteOp getOrCreateDiscreteOp(int op) { 960 DiscreteOp result = mPackageOps.get(op); 961 if (result == null) { 962 result = new DiscreteOp(); 963 mPackageOps.put(op, result); 964 } 965 return result; 966 } 967 applyToHistory(AppOpsManager.HistoricalOps result, int uid, @NonNull String packageName, @NonNull ArrayMap<Integer, AttributionChain> attributionChains)968 private void applyToHistory(AppOpsManager.HistoricalOps result, int uid, 969 @NonNull String packageName, 970 @NonNull ArrayMap<Integer, AttributionChain> attributionChains) { 971 int nPackageOps = mPackageOps.size(); 972 for (int i = 0; i < nPackageOps; i++) { 973 mPackageOps.valueAt(i).applyToHistory(result, uid, packageName, 974 mPackageOps.keyAt(i), attributionChains); 975 } 976 } 977 serialize(TypedXmlSerializer out)978 void serialize(TypedXmlSerializer out) throws Exception { 979 int nOps = mPackageOps.size(); 980 for (int i = 0; i < nOps; i++) { 981 out.startTag(null, TAG_OP); 982 out.attributeInt(null, ATTR_OP_ID, mPackageOps.keyAt(i)); 983 mPackageOps.valueAt(i).serialize(out); 984 out.endTag(null, TAG_OP); 985 } 986 } 987 dump(@onNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)988 private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf, 989 @NonNull Date date, @NonNull String prefix, int nDiscreteOps) { 990 int nOps = mPackageOps.size(); 991 for (int i = 0; i < nOps; i++) { 992 pw.print(prefix); 993 pw.print(AppOpsManager.opToName(mPackageOps.keyAt(i))); 994 pw.println(); 995 mPackageOps.valueAt(i).dump(pw, sdf, date, prefix + " ", nDiscreteOps); 996 } 997 } 998 deserialize(TypedXmlPullParser parser, long beginTimeMillis)999 void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception { 1000 int depth = parser.getDepth(); 1001 while (XmlUtils.nextElementWithin(parser, depth)) { 1002 if (TAG_OP.equals(parser.getName())) { 1003 int op = parser.getAttributeInt(null, ATTR_OP_ID); 1004 getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis); 1005 } 1006 } 1007 } 1008 } 1009 1010 static final class DiscreteOp { 1011 ArrayMap<String, DiscreteDeviceOp> mDeviceAttributedOps; 1012 DiscreteOp()1013 DiscreteOp() { 1014 mDeviceAttributedOps = new ArrayMap<>(); 1015 } 1016 isEmpty()1017 boolean isEmpty() { 1018 return mDeviceAttributedOps.isEmpty(); 1019 } 1020 merge(DiscreteOp other)1021 void merge(DiscreteOp other) { 1022 int nDevices = other.mDeviceAttributedOps.size(); 1023 for (int i = 0; i < nDevices; i++) { 1024 String deviceId = other.mDeviceAttributedOps.keyAt(i); 1025 DiscreteDeviceOp otherDeviceOps = other.mDeviceAttributedOps.valueAt(i); 1026 getOrCreateDiscreteDeviceOp(deviceId).merge(otherDeviceOps); 1027 } 1028 } 1029 1030 // Note: Update this method when we want to filter by device Id. filter(long beginTimeMillis, long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, int currentUid, String currentPkgName, int currentOp, ArrayMap<Integer, AttributionChain> attributionChains)1031 private void filter(long beginTimeMillis, long endTimeMillis, 1032 @AppOpsManager.HistoricalOpsRequestFilter int filter, 1033 @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, 1034 int currentUid, String currentPkgName, int currentOp, 1035 ArrayMap<Integer, AttributionChain> attributionChains) { 1036 int nDevices = mDeviceAttributedOps.size(); 1037 for (int i = nDevices - 1; i >= 0; i--) { 1038 mDeviceAttributedOps.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, 1039 attributionTagFilter, flagsFilter, currentUid, currentPkgName, currentOp, 1040 attributionChains); 1041 if (mDeviceAttributedOps.valueAt(i).isEmpty()) { 1042 mDeviceAttributedOps.removeAt(i); 1043 } 1044 } 1045 } 1046 offsetHistory(long offset)1047 private void offsetHistory(long offset) { 1048 int nDevices = mDeviceAttributedOps.size(); 1049 for (int i = 0; i < nDevices; i++) { 1050 mDeviceAttributedOps.valueAt(i).offsetHistory(offset); 1051 } 1052 } 1053 addDiscreteAccess(@onNull String deviceId, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)1054 void addDiscreteAccess(@NonNull String deviceId, @Nullable String attributionTag, 1055 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, 1056 long accessTime, long accessDuration, 1057 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { 1058 getOrCreateDiscreteDeviceOp(deviceId).addDiscreteAccess(attributionTag, flags, uidState, 1059 accessTime, accessDuration, attributionFlags, attributionChainId); 1060 } 1061 getOrCreateDiscreteDeviceOp(String deviceId)1062 private DiscreteDeviceOp getOrCreateDiscreteDeviceOp(String deviceId) { 1063 return mDeviceAttributedOps.computeIfAbsent(deviceId, k -> new DiscreteDeviceOp()); 1064 } 1065 1066 // TODO: b/308716962 Retrieve discrete histories from all devices and integrate them with 1067 // HistoricalOps applyToHistory(AppOpsManager.HistoricalOps result, int uid, @NonNull String packageName, int op, @NonNull ArrayMap<Integer, AttributionChain> attributionChains)1068 private void applyToHistory(AppOpsManager.HistoricalOps result, int uid, 1069 @NonNull String packageName, int op, 1070 @NonNull ArrayMap<Integer, AttributionChain> attributionChains) { 1071 if (mDeviceAttributedOps.get(PERSISTENT_DEVICE_ID_DEFAULT) != null) { 1072 mDeviceAttributedOps.get(PERSISTENT_DEVICE_ID_DEFAULT).applyToHistory(result, uid, 1073 packageName, op, attributionChains); 1074 } 1075 } 1076 dump(@onNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)1077 private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf, 1078 @NonNull Date date, @NonNull String prefix, int nDiscreteOps) { 1079 int nDevices = mDeviceAttributedOps.size(); 1080 for (int i = 0; i < nDevices; i++) { 1081 pw.print(prefix); 1082 pw.print("Device: "); 1083 pw.print(mDeviceAttributedOps.keyAt(i)); 1084 pw.println(); 1085 mDeviceAttributedOps.valueAt(i).dump(pw, sdf, date, prefix + " ", 1086 nDiscreteOps); 1087 } 1088 } 1089 serialize(TypedXmlSerializer out)1090 void serialize(TypedXmlSerializer out) throws Exception { 1091 int nDevices = mDeviceAttributedOps.size(); 1092 for (int i = 0; i < nDevices; i++) { 1093 String deviceId = mDeviceAttributedOps.keyAt(i); 1094 mDeviceAttributedOps.valueAt(i).serialize(out, deviceId); 1095 } 1096 } 1097 deserialize(TypedXmlPullParser parser, long beginTimeMillis)1098 void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception { 1099 int outerDepth = parser.getDepth(); 1100 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1101 if (TAG_TAG.equals(parser.getName())) { 1102 String attributionTag = parser.getAttributeValue(null, ATTR_TAG); 1103 1104 int innerDepth = parser.getDepth(); 1105 while (XmlUtils.nextElementWithin(parser, innerDepth)) { 1106 if (TAG_ENTRY.equals(parser.getName())) { 1107 long noteTime = parser.getAttributeLong(null, ATTR_NOTE_TIME); 1108 long noteDuration = parser.getAttributeLong(null, ATTR_NOTE_DURATION, 1109 -1); 1110 int uidState = parser.getAttributeInt(null, ATTR_UID_STATE); 1111 int opFlags = parser.getAttributeInt(null, ATTR_FLAGS); 1112 int attributionFlags = parser.getAttributeInt(null, 1113 ATTR_ATTRIBUTION_FLAGS, AppOpsManager.ATTRIBUTION_FLAGS_NONE); 1114 int attributionChainId = parser.getAttributeInt(null, ATTR_CHAIN_ID, 1115 AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); 1116 String deviceId = parser.getAttributeValue(null, ATTR_DEVICE_ID); 1117 if (deviceId == null) { 1118 deviceId = PERSISTENT_DEVICE_ID_DEFAULT; 1119 } 1120 if (noteTime + noteDuration < beginTimeMillis) { 1121 continue; 1122 } 1123 1124 DiscreteDeviceOp deviceOps = getOrCreateDiscreteDeviceOp(deviceId); 1125 List<DiscreteOpEvent> events = 1126 deviceOps.getOrCreateDiscreteOpEventsList(attributionTag); 1127 DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration, 1128 uidState, opFlags, attributionFlags, attributionChainId); 1129 events.add(event); 1130 } 1131 } 1132 } 1133 } 1134 1135 int nDeviceOps = mDeviceAttributedOps.size(); 1136 for (int i = 0; i < nDeviceOps; i++) { 1137 DiscreteDeviceOp deviceOp = mDeviceAttributedOps.valueAt(i); 1138 1139 int nAttrOps = deviceOp.mAttributedOps.size(); 1140 for (int j = 0; j < nAttrOps; j++) { 1141 List<DiscreteOpEvent> events = deviceOp.mAttributedOps.valueAt(j); 1142 events.sort(Comparator.comparingLong(a -> a.mNoteTime)); 1143 } 1144 } 1145 } 1146 } 1147 1148 static final class DiscreteDeviceOp { 1149 ArrayMap<String, List<DiscreteOpEvent>> mAttributedOps; 1150 DiscreteDeviceOp()1151 DiscreteDeviceOp() { 1152 mAttributedOps = new ArrayMap<>(); 1153 } 1154 isEmpty()1155 boolean isEmpty() { 1156 return mAttributedOps.isEmpty(); 1157 } 1158 merge(DiscreteDeviceOp other)1159 void merge(DiscreteDeviceOp other) { 1160 int nTags = other.mAttributedOps.size(); 1161 for (int i = 0; i < nTags; i++) { 1162 String tag = other.mAttributedOps.keyAt(i); 1163 List<DiscreteOpEvent> otherEvents = other.mAttributedOps.valueAt(i); 1164 List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList(tag); 1165 mAttributedOps.put(tag, stableListMerge(events, otherEvents)); 1166 } 1167 } 1168 filter(long beginTimeMillis, long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, int currentUid, String currentPkgName, int currentOp, ArrayMap<Integer, AttributionChain> attributionChains)1169 private void filter(long beginTimeMillis, long endTimeMillis, 1170 @AppOpsManager.HistoricalOpsRequestFilter int filter, 1171 @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, 1172 int currentUid, String currentPkgName, int currentOp, 1173 ArrayMap<Integer, AttributionChain> attributionChains) { 1174 if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0) { 1175 ArrayMap<String, List<DiscreteOpEvent>> attributedOps = new ArrayMap<>(); 1176 attributedOps.put(attributionTagFilter, 1177 getOrCreateDiscreteOpEventsList(attributionTagFilter)); 1178 mAttributedOps = attributedOps; 1179 } 1180 1181 int nTags = mAttributedOps.size(); 1182 for (int i = nTags - 1; i >= 0; i--) { 1183 String tag = mAttributedOps.keyAt(i); 1184 List<DiscreteOpEvent> list = mAttributedOps.valueAt(i); 1185 list = filterEventsList(list, beginTimeMillis, endTimeMillis, flagsFilter, 1186 currentUid, currentPkgName, currentOp, mAttributedOps.keyAt(i), 1187 attributionChains); 1188 mAttributedOps.put(tag, list); 1189 if (list.isEmpty()) { 1190 mAttributedOps.removeAt(i); 1191 } 1192 } 1193 } 1194 offsetHistory(long offset)1195 private void offsetHistory(long offset) { 1196 int nTags = mAttributedOps.size(); 1197 for (int i = 0; i < nTags; i++) { 1198 List<DiscreteOpEvent> list = mAttributedOps.valueAt(i); 1199 1200 int n = list.size(); 1201 for (int j = 0; j < n; j++) { 1202 DiscreteOpEvent event = list.get(j); 1203 list.set(j, new DiscreteOpEvent(event.mNoteTime - offset, event.mNoteDuration, 1204 event.mUidState, event.mOpFlag, event.mAttributionFlags, 1205 event.mAttributionChainId)); 1206 } 1207 } 1208 } 1209 addDiscreteAccess(@ullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)1210 void addDiscreteAccess(@Nullable String attributionTag, 1211 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, 1212 long accessTime, long accessDuration, 1213 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { 1214 List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList( 1215 attributionTag); 1216 1217 int i = attributedOps.size(); 1218 for (; i > 0; i--) { 1219 DiscreteOpEvent previousOp = attributedOps.get(i - 1); 1220 if (discretizeTimeStamp(previousOp.mNoteTime) < discretizeTimeStamp(accessTime)) { 1221 break; 1222 } 1223 if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState 1224 && previousOp.mAttributionFlags == attributionFlags 1225 && previousOp.mAttributionChainId == attributionChainId) { 1226 if (discretizeDuration(accessDuration) != discretizeDuration( 1227 previousOp.mNoteDuration)) { 1228 break; 1229 } else { 1230 return; 1231 } 1232 } 1233 } 1234 attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags, 1235 attributionFlags, attributionChainId)); 1236 } 1237 getOrCreateDiscreteOpEventsList(String attributionTag)1238 private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) { 1239 return mAttributedOps.computeIfAbsent(attributionTag, 1240 k -> new ArrayList<>()); 1241 } 1242 applyToHistory(AppOpsManager.HistoricalOps result, int uid, @NonNull String packageName, int op, @NonNull ArrayMap<Integer, AttributionChain> attributionChains)1243 private void applyToHistory(AppOpsManager.HistoricalOps result, int uid, 1244 @NonNull String packageName, int op, 1245 @NonNull ArrayMap<Integer, AttributionChain> attributionChains) { 1246 int nOps = mAttributedOps.size(); 1247 for (int i = 0; i < nOps; i++) { 1248 String tag = mAttributedOps.keyAt(i); 1249 List<DiscreteOpEvent> events = mAttributedOps.valueAt(i); 1250 int nEvents = events.size(); 1251 for (int j = 0; j < nEvents; j++) { 1252 DiscreteOpEvent event = events.get(j); 1253 AppOpsManager.OpEventProxyInfo proxy = null; 1254 if (event.mAttributionChainId != ATTRIBUTION_CHAIN_ID_NONE) { 1255 AttributionChain chain = attributionChains.get(event.mAttributionChainId); 1256 if (chain != null && chain.isComplete() 1257 && chain.isStart(packageName, uid, tag, op, event) 1258 && chain.mLastVisibleEvent != null) { 1259 AttributionChain.OpEvent proxyEvent = chain.mLastVisibleEvent; 1260 proxy = new AppOpsManager.OpEventProxyInfo(proxyEvent.mUid, 1261 proxyEvent.mPkgName, proxyEvent.mAttributionTag); 1262 } 1263 } 1264 result.addDiscreteAccess(op, uid, packageName, tag, event.mUidState, 1265 event.mOpFlag, discretizeTimeStamp(event.mNoteTime), 1266 discretizeDuration(event.mNoteDuration), proxy); 1267 } 1268 } 1269 } 1270 dump(@onNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)1271 private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf, 1272 @NonNull Date date, @NonNull String prefix, int nDiscreteOps) { 1273 int nAttributions = mAttributedOps.size(); 1274 for (int i = 0; i < nAttributions; i++) { 1275 pw.print(prefix); 1276 pw.print("Attribution: "); 1277 pw.print(mAttributedOps.keyAt(i)); 1278 pw.println(); 1279 List<DiscreteOpEvent> ops = mAttributedOps.valueAt(i); 1280 int nOps = ops.size(); 1281 int first = nDiscreteOps < 1 ? 0 : max(0, nOps - nDiscreteOps); 1282 for (int j = first; j < nOps; j++) { 1283 ops.get(j).dump(pw, sdf, date, prefix + " "); 1284 } 1285 } 1286 } 1287 1288 void serialize(TypedXmlSerializer out, String deviceId) throws Exception { 1289 int nAttributions = mAttributedOps.size(); 1290 for (int i = 0; i < nAttributions; i++) { 1291 out.startTag(null, TAG_TAG); 1292 String tag = mAttributedOps.keyAt(i); 1293 if (tag != null) { 1294 out.attribute(null, ATTR_TAG, tag); 1295 } 1296 List<DiscreteOpEvent> ops = mAttributedOps.valueAt(i); 1297 int nOps = ops.size(); 1298 for (int j = 0; j < nOps; j++) { 1299 out.startTag(null, TAG_ENTRY); 1300 ops.get(j).serialize(out, deviceId); 1301 out.endTag(null, TAG_ENTRY); 1302 } 1303 out.endTag(null, TAG_TAG); 1304 } 1305 } 1306 } 1307 1308 static final class DiscreteOpEvent { 1309 final long mNoteTime; 1310 final long mNoteDuration; 1311 final @AppOpsManager.UidState int mUidState; 1312 final @AppOpsManager.OpFlags int mOpFlag; 1313 final @AppOpsManager.AttributionFlags int mAttributionFlags; 1314 final int mAttributionChainId; 1315 1316 DiscreteOpEvent(long noteTime, long noteDuration, @AppOpsManager.UidState int uidState, 1317 @AppOpsManager.OpFlags int opFlag, 1318 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) { 1319 mNoteTime = noteTime; 1320 mNoteDuration = noteDuration; 1321 mUidState = uidState; 1322 mOpFlag = opFlag; 1323 mAttributionFlags = attributionFlags; 1324 mAttributionChainId = attributionChainId; 1325 } 1326 1327 public boolean equalsExceptDuration(DiscreteOpEvent o) { 1328 return mNoteTime == o.mNoteTime && mUidState == o.mUidState && mOpFlag == o.mOpFlag 1329 && mAttributionFlags == o.mAttributionFlags 1330 && mAttributionChainId == o.mAttributionChainId; 1331 1332 } 1333 1334 private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf, 1335 @NonNull Date date, @NonNull String prefix) { 1336 pw.print(prefix); 1337 pw.print("Access ["); 1338 pw.print(getUidStateName(mUidState)); 1339 pw.print("-"); 1340 pw.print(flagsToString(mOpFlag)); 1341 pw.print("] at "); 1342 date.setTime(mNoteTime); 1343 pw.print(sdf.format(date)); 1344 if (mNoteDuration != -1) { 1345 pw.print(" for "); 1346 pw.print(mNoteDuration); 1347 pw.print(" milliseconds "); 1348 } 1349 if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) { 1350 pw.print(" attribution flags="); 1351 pw.print(mAttributionFlags); 1352 pw.print(" with chainId="); 1353 pw.print(mAttributionChainId); 1354 } 1355 pw.println(); 1356 } 1357 1358 private void serialize(TypedXmlSerializer out, String deviceId) throws Exception { 1359 out.attributeLong(null, ATTR_NOTE_TIME, mNoteTime); 1360 if (mNoteDuration != -1) { 1361 out.attributeLong(null, ATTR_NOTE_DURATION, mNoteDuration); 1362 } 1363 if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) { 1364 out.attributeInt(null, ATTR_ATTRIBUTION_FLAGS, mAttributionFlags); 1365 } 1366 if (mAttributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE) { 1367 out.attributeInt(null, ATTR_CHAIN_ID, mAttributionChainId); 1368 } 1369 if (!Objects.equals(deviceId, PERSISTENT_DEVICE_ID_DEFAULT)) { 1370 out.attribute(null, ATTR_DEVICE_ID, deviceId); 1371 } 1372 out.attributeInt(null, ATTR_UID_STATE, mUidState); 1373 out.attributeInt(null, ATTR_FLAGS, mOpFlag); 1374 } 1375 } 1376 1377 private static List<DiscreteOpEvent> stableListMerge(List<DiscreteOpEvent> a, 1378 List<DiscreteOpEvent> b) { 1379 int nA = a.size(); 1380 int nB = b.size(); 1381 int i = 0; 1382 int k = 0; 1383 List<DiscreteOpEvent> result = new ArrayList<>(nA + nB); 1384 while (i < nA || k < nB) { 1385 if (i == nA) { 1386 result.add(b.get(k++)); 1387 } else if (k == nB) { 1388 result.add(a.get(i++)); 1389 } else if (a.get(i).mNoteTime < b.get(k).mNoteTime) { 1390 result.add(a.get(i++)); 1391 } else { 1392 result.add(b.get(k++)); 1393 } 1394 } 1395 return result; 1396 } 1397 1398 private static List<DiscreteOpEvent> filterEventsList(List<DiscreteOpEvent> list, 1399 long beginTimeMillis, long endTimeMillis, @AppOpsManager.OpFlags int flagsFilter, 1400 int currentUid, String currentPackageName, int currentOp, String currentAttrTag, 1401 ArrayMap<Integer, AttributionChain> attributionChains) { 1402 int n = list.size(); 1403 List<DiscreteOpEvent> result = new ArrayList<>(n); 1404 for (int i = 0; i < n; i++) { 1405 DiscreteOpEvent event = list.get(i); 1406 AttributionChain chain = attributionChains.get(event.mAttributionChainId); 1407 // If we have an attribution chain, and this event isn't the beginning node, remove it 1408 if (chain != null && !chain.isStart(currentPackageName, currentUid, currentAttrTag, 1409 currentOp, event) && chain.isComplete() 1410 && event.mAttributionChainId != ATTRIBUTION_CHAIN_ID_NONE) { 1411 continue; 1412 } 1413 if ((event.mOpFlag & flagsFilter) != 0 1414 && event.mNoteTime + event.mNoteDuration > beginTimeMillis 1415 && event.mNoteTime < endTimeMillis) { 1416 result.add(event); 1417 } 1418 } 1419 return result; 1420 } 1421 } 1422