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