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