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.Xml; 57 58 import com.android.internal.annotations.GuardedBy; 59 import com.android.internal.util.ArrayUtils; 60 import com.android.internal.util.XmlUtils; 61 import com.android.modules.utils.TypedXmlPullParser; 62 import com.android.modules.utils.TypedXmlSerializer; 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); 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