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