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