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