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