• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.TypedXmlPullParser;
57 import android.util.TypedXmlSerializer;
58 import android.util.Xml;
59 
60 import com.android.internal.annotations.GuardedBy;
61 import com.android.internal.util.ArrayUtils;
62 import com.android.internal.util.XmlUtils;
63 
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.getMessage() + " "
820                             + t.getStackTrace());
821                 }
822             }
823         }
824     }
825 
createDiscreteAccessDirLocked()826     private void createDiscreteAccessDirLocked() {
827         if (!mDiscreteAccessDir.exists()) {
828             if (!mDiscreteAccessDir.mkdirs()) {
829                 Slog.e(TAG, "Failed to create DiscreteRegistry directory");
830             }
831             FileUtils.setPermissions(mDiscreteAccessDir.getPath(),
832                     FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1);
833         }
834     }
835 
836     private final class DiscreteUidOps {
837         ArrayMap<String, DiscretePackageOps> mPackages;
838 
DiscreteUidOps()839         DiscreteUidOps() {
840             mPackages = new ArrayMap<>();
841         }
842 
isEmpty()843         boolean isEmpty() {
844             return mPackages.isEmpty();
845         }
846 
merge(DiscreteUidOps other)847         void merge(DiscreteUidOps other) {
848             int nPackages = other.mPackages.size();
849             for (int i = 0; i < nPackages; i++) {
850                 String packageName = other.mPackages.keyAt(i);
851                 DiscretePackageOps p = other.mPackages.valueAt(i);
852                 getOrCreateDiscretePackageOps(packageName).merge(p);
853             }
854         }
855 
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)856         private void filter(long beginTimeMillis, long endTimeMillis,
857                 @AppOpsManager.HistoricalOpsRequestFilter int filter,
858                 @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
859                 @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter,
860                 int currentUid, ArrayMap<Integer, AttributionChain> attributionChains) {
861             if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
862                 ArrayMap<String, DiscretePackageOps> packages = new ArrayMap<>();
863                 packages.put(packageNameFilter, getOrCreateDiscretePackageOps(packageNameFilter));
864                 mPackages = packages;
865             }
866             int nPackages = mPackages.size();
867             for (int i = nPackages - 1; i >= 0; i--) {
868                 mPackages.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter, opNamesFilter,
869                         attributionTagFilter, flagsFilter, currentUid, mPackages.keyAt(i),
870                         attributionChains);
871                 if (mPackages.valueAt(i).isEmpty()) {
872                     mPackages.removeAt(i);
873                 }
874             }
875         }
876 
offsetHistory(long offset)877         private void offsetHistory(long offset) {
878             int nPackages = mPackages.size();
879             for (int i = 0; i < nPackages; i++) {
880                 mPackages.valueAt(i).offsetHistory(offset);
881             }
882         }
883 
clearPackage(String packageName)884         private void clearPackage(String packageName) {
885             mPackages.remove(packageName);
886         }
887 
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)888         void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag,
889                 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
890                 long accessTime, long accessDuration,
891                 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
892             getOrCreateDiscretePackageOps(packageName).addDiscreteAccess(op, attributionTag, flags,
893                     uidState, accessTime, accessDuration, attributionFlags, attributionChainId);
894         }
895 
getOrCreateDiscretePackageOps(String packageName)896         private DiscretePackageOps getOrCreateDiscretePackageOps(String packageName) {
897             DiscretePackageOps result = mPackages.get(packageName);
898             if (result == null) {
899                 result = new DiscretePackageOps();
900                 mPackages.put(packageName, result);
901             }
902             return result;
903         }
904 
applyToHistory(AppOpsManager.HistoricalOps result, int uid, @NonNull ArrayMap<Integer, AttributionChain> attributionChains)905         private void applyToHistory(AppOpsManager.HistoricalOps result, int uid,
906                 @NonNull ArrayMap<Integer, AttributionChain> attributionChains) {
907             int nPackages = mPackages.size();
908             for (int i = 0; i < nPackages; i++) {
909                 mPackages.valueAt(i).applyToHistory(result, uid, mPackages.keyAt(i),
910                         attributionChains);
911             }
912         }
913 
serialize(TypedXmlSerializer out)914         void serialize(TypedXmlSerializer out) throws Exception {
915             int nPackages = mPackages.size();
916             for (int i = 0; i < nPackages; i++) {
917                 out.startTag(null, TAG_PACKAGE);
918                 out.attribute(null, ATTR_PACKAGE_NAME, mPackages.keyAt(i));
919                 mPackages.valueAt(i).serialize(out);
920                 out.endTag(null, TAG_PACKAGE);
921             }
922         }
923 
dump(@onNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)924         private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf,
925                 @NonNull Date date, @NonNull String prefix, int nDiscreteOps) {
926             int nPackages = mPackages.size();
927             for (int i = 0; i < nPackages; i++) {
928                 pw.print(prefix);
929                 pw.print("Package: ");
930                 pw.print(mPackages.keyAt(i));
931                 pw.println();
932                 mPackages.valueAt(i).dump(pw, sdf, date, prefix + "  ", nDiscreteOps);
933             }
934         }
935 
deserialize(TypedXmlPullParser parser, long beginTimeMillis)936         void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception {
937             int depth = parser.getDepth();
938             while (XmlUtils.nextElementWithin(parser, depth)) {
939                 if (TAG_PACKAGE.equals(parser.getName())) {
940                     String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
941                     getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis);
942                 }
943             }
944         }
945     }
946 
947     private final class DiscretePackageOps {
948         ArrayMap<Integer, DiscreteOp> mPackageOps;
949 
DiscretePackageOps()950         DiscretePackageOps() {
951             mPackageOps = new ArrayMap<>();
952         }
953 
isEmpty()954         boolean isEmpty() {
955             return mPackageOps.isEmpty();
956         }
957 
addDiscreteAccess(int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)958         void addDiscreteAccess(int op, @Nullable String attributionTag,
959                 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
960                 long accessTime, long accessDuration,
961                 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
962             getOrCreateDiscreteOp(op).addDiscreteAccess(attributionTag, flags, uidState, accessTime,
963                     accessDuration, attributionFlags, attributionChainId);
964         }
965 
merge(DiscretePackageOps other)966         void merge(DiscretePackageOps other) {
967             int nOps = other.mPackageOps.size();
968             for (int i = 0; i < nOps; i++) {
969                 int opId = other.mPackageOps.keyAt(i);
970                 DiscreteOp op = other.mPackageOps.valueAt(i);
971                 getOrCreateDiscreteOp(opId).merge(op);
972             }
973         }
974 
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)975         private void filter(long beginTimeMillis, long endTimeMillis,
976                 @AppOpsManager.HistoricalOpsRequestFilter int filter,
977                 @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
978                 @AppOpsManager.OpFlags int flagsFilter, int currentUid, String currentPkgName,
979                 ArrayMap<Integer, AttributionChain> attributionChains) {
980             int nOps = mPackageOps.size();
981             for (int i = nOps - 1; i >= 0; i--) {
982                 int opId = mPackageOps.keyAt(i);
983                 if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter,
984                         AppOpsManager.opToPublicName(opId))) {
985                     mPackageOps.removeAt(i);
986                     continue;
987                 }
988                 mPackageOps.valueAt(i).filter(beginTimeMillis, endTimeMillis, filter,
989                         attributionTagFilter, flagsFilter, currentUid, currentPkgName,
990                         mPackageOps.keyAt(i), attributionChains);
991                 if (mPackageOps.valueAt(i).isEmpty()) {
992                     mPackageOps.removeAt(i);
993                 }
994             }
995         }
996 
offsetHistory(long offset)997         private void offsetHistory(long offset) {
998             int nOps = mPackageOps.size();
999             for (int i = 0; i < nOps; i++) {
1000                 mPackageOps.valueAt(i).offsetHistory(offset);
1001             }
1002         }
1003 
getOrCreateDiscreteOp(int op)1004         private DiscreteOp getOrCreateDiscreteOp(int op) {
1005             DiscreteOp result = mPackageOps.get(op);
1006             if (result == null) {
1007                 result = new DiscreteOp();
1008                 mPackageOps.put(op, result);
1009             }
1010             return result;
1011         }
1012 
applyToHistory(AppOpsManager.HistoricalOps result, int uid, @NonNull String packageName, @NonNull ArrayMap<Integer, AttributionChain> attributionChains)1013         private void applyToHistory(AppOpsManager.HistoricalOps result, int uid,
1014                 @NonNull String packageName,
1015                 @NonNull ArrayMap<Integer, AttributionChain> attributionChains) {
1016             int nPackageOps = mPackageOps.size();
1017             for (int i = 0; i < nPackageOps; i++) {
1018                 mPackageOps.valueAt(i).applyToHistory(result, uid, packageName,
1019                         mPackageOps.keyAt(i), attributionChains);
1020             }
1021         }
1022 
serialize(TypedXmlSerializer out)1023         void serialize(TypedXmlSerializer out) throws Exception {
1024             int nOps = mPackageOps.size();
1025             for (int i = 0; i < nOps; i++) {
1026                 out.startTag(null, TAG_OP);
1027                 out.attributeInt(null, ATTR_OP_ID, mPackageOps.keyAt(i));
1028                 mPackageOps.valueAt(i).serialize(out);
1029                 out.endTag(null, TAG_OP);
1030             }
1031         }
1032 
dump(@onNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)1033         private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf,
1034                 @NonNull Date date, @NonNull String prefix, int nDiscreteOps) {
1035             int nOps = mPackageOps.size();
1036             for (int i = 0; i < nOps; i++) {
1037                 pw.print(prefix);
1038                 pw.print(AppOpsManager.opToName(mPackageOps.keyAt(i)));
1039                 pw.println();
1040                 mPackageOps.valueAt(i).dump(pw, sdf, date, prefix + "  ", nDiscreteOps);
1041             }
1042         }
1043 
deserialize(TypedXmlPullParser parser, long beginTimeMillis)1044         void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception {
1045             int depth = parser.getDepth();
1046             while (XmlUtils.nextElementWithin(parser, depth)) {
1047                 if (TAG_OP.equals(parser.getName())) {
1048                     int op = parser.getAttributeInt(null, ATTR_OP_ID);
1049                     getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis);
1050                 }
1051             }
1052         }
1053     }
1054 
1055     private final class DiscreteOp {
1056         ArrayMap<String, List<DiscreteOpEvent>> mAttributedOps;
1057 
DiscreteOp()1058         DiscreteOp() {
1059             mAttributedOps = new ArrayMap<>();
1060         }
1061 
isEmpty()1062         boolean isEmpty() {
1063             return mAttributedOps.isEmpty();
1064         }
1065 
merge(DiscreteOp other)1066         void merge(DiscreteOp other) {
1067             int nTags = other.mAttributedOps.size();
1068             for (int i = 0; i < nTags; i++) {
1069                 String tag = other.mAttributedOps.keyAt(i);
1070                 List<DiscreteOpEvent> otherEvents = other.mAttributedOps.valueAt(i);
1071                 List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList(tag);
1072                 mAttributedOps.put(tag, stableListMerge(events, otherEvents));
1073             }
1074         }
1075 
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)1076         private void filter(long beginTimeMillis, long endTimeMillis,
1077                 @AppOpsManager.HistoricalOpsRequestFilter int filter,
1078                 @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter,
1079                 int currentUid, String currentPkgName, int currentOp,
1080                 ArrayMap<Integer, AttributionChain> attributionChains) {
1081             if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0) {
1082                 ArrayMap<String, List<DiscreteOpEvent>> attributedOps = new ArrayMap<>();
1083                 attributedOps.put(attributionTagFilter,
1084                         getOrCreateDiscreteOpEventsList(attributionTagFilter));
1085                 mAttributedOps = attributedOps;
1086             }
1087 
1088             int nTags = mAttributedOps.size();
1089             for (int i = nTags - 1; i >= 0; i--) {
1090                 String tag = mAttributedOps.keyAt(i);
1091                 List<DiscreteOpEvent> list = mAttributedOps.valueAt(i);
1092                 list = filterEventsList(list, beginTimeMillis, endTimeMillis, flagsFilter,
1093                         currentUid, currentPkgName, currentOp, mAttributedOps.keyAt(i),
1094                         attributionChains);
1095                 mAttributedOps.put(tag, list);
1096                 if (list.size() == 0) {
1097                     mAttributedOps.removeAt(i);
1098                 }
1099             }
1100         }
1101 
offsetHistory(long offset)1102         private void offsetHistory(long offset) {
1103             int nTags = mAttributedOps.size();
1104             for (int i = 0; i < nTags; i++) {
1105                 List<DiscreteOpEvent> list = mAttributedOps.valueAt(i);
1106 
1107                 int n = list.size();
1108                 for (int j = 0; j < n; j++) {
1109                     DiscreteOpEvent event = list.get(j);
1110                     list.set(j, new DiscreteOpEvent(event.mNoteTime - offset, event.mNoteDuration,
1111                             event.mUidState, event.mOpFlag, event.mAttributionFlags,
1112                             event.mAttributionChainId));
1113                 }
1114             }
1115         }
1116 
addDiscreteAccess(@ullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)1117         void addDiscreteAccess(@Nullable String attributionTag,
1118                 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
1119                 long accessTime, long accessDuration,
1120                 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
1121             List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList(
1122                     attributionTag);
1123 
1124             int nAttributedOps = attributedOps.size();
1125             int i = nAttributedOps;
1126             for (; i > 0; i--) {
1127                 DiscreteOpEvent previousOp = attributedOps.get(i - 1);
1128                 if (discretizeTimeStamp(previousOp.mNoteTime) < discretizeTimeStamp(accessTime)) {
1129                     break;
1130                 }
1131                 if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState
1132                         && previousOp.mAttributionFlags == attributionFlags
1133                         && previousOp.mAttributionChainId == attributionChainId) {
1134                     if (discretizeDuration(accessDuration) != discretizeDuration(
1135                             previousOp.mNoteDuration)) {
1136                         break;
1137                     } else {
1138                         return;
1139                     }
1140                 }
1141             }
1142             attributedOps.add(i, new DiscreteOpEvent(accessTime, accessDuration, uidState, flags,
1143                     attributionFlags, attributionChainId));
1144         }
1145 
getOrCreateDiscreteOpEventsList(String attributionTag)1146         private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) {
1147             List<DiscreteOpEvent> result = mAttributedOps.get(attributionTag);
1148             if (result == null) {
1149                 result = new ArrayList<>();
1150                 mAttributedOps.put(attributionTag, result);
1151             }
1152             return result;
1153         }
1154 
applyToHistory(AppOpsManager.HistoricalOps result, int uid, @NonNull String packageName, int op, @NonNull ArrayMap<Integer, AttributionChain> attributionChains)1155         private void applyToHistory(AppOpsManager.HistoricalOps result, int uid,
1156                 @NonNull String packageName, int op,
1157                 @NonNull ArrayMap<Integer, AttributionChain> attributionChains) {
1158             int nOps = mAttributedOps.size();
1159             for (int i = 0; i < nOps; i++) {
1160                 String tag = mAttributedOps.keyAt(i);
1161                 List<DiscreteOpEvent> events = mAttributedOps.valueAt(i);
1162                 int nEvents = events.size();
1163                 for (int j = 0; j < nEvents; j++) {
1164                     DiscreteOpEvent event = events.get(j);
1165                     AppOpsManager.OpEventProxyInfo proxy = null;
1166                     if (event.mAttributionChainId != ATTRIBUTION_CHAIN_ID_NONE
1167                             && attributionChains != null) {
1168                         AttributionChain chain = attributionChains.get(event.mAttributionChainId);
1169                         if (chain != null && chain.isComplete()
1170                                 && chain.isStart(packageName, uid, tag, op, event)
1171                                 && chain.mLastVisibleEvent != null) {
1172                             AttributionChain.OpEvent proxyEvent = chain.mLastVisibleEvent;
1173                             proxy = new AppOpsManager.OpEventProxyInfo(proxyEvent.mUid,
1174                                     proxyEvent.mPkgName, proxyEvent.mAttributionTag);
1175                         }
1176                     }
1177                     result.addDiscreteAccess(op, uid, packageName, tag, event.mUidState,
1178                             event.mOpFlag, discretizeTimeStamp(event.mNoteTime),
1179                             discretizeDuration(event.mNoteDuration), proxy);
1180                 }
1181             }
1182         }
1183 
dump(@onNull PrintWriter pw, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)1184         private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf,
1185                 @NonNull Date date, @NonNull String prefix, int nDiscreteOps) {
1186             int nAttributions = mAttributedOps.size();
1187             for (int i = 0; i < nAttributions; i++) {
1188                 pw.print(prefix);
1189                 pw.print("Attribution: ");
1190                 pw.print(mAttributedOps.keyAt(i));
1191                 pw.println();
1192                 List<DiscreteOpEvent> ops = mAttributedOps.valueAt(i);
1193                 int nOps = ops.size();
1194                 int first = nDiscreteOps < 1 ? 0 : max(0, nOps - nDiscreteOps);
1195                 for (int j = first; j < nOps; j++) {
1196                     ops.get(j).dump(pw, sdf, date, prefix + "  ");
1197 
1198                 }
1199             }
1200         }
1201 
1202         void serialize(TypedXmlSerializer out) throws Exception {
1203             int nAttributions = mAttributedOps.size();
1204             for (int i = 0; i < nAttributions; i++) {
1205                 out.startTag(null, TAG_TAG);
1206                 String tag = mAttributedOps.keyAt(i);
1207                 if (tag != null) {
1208                     out.attribute(null, ATTR_TAG, mAttributedOps.keyAt(i));
1209                 }
1210                 List<DiscreteOpEvent> ops = mAttributedOps.valueAt(i);
1211                 int nOps = ops.size();
1212                 for (int j = 0; j < nOps; j++) {
1213                     out.startTag(null, TAG_ENTRY);
1214                     ops.get(j).serialize(out);
1215                     out.endTag(null, TAG_ENTRY);
1216                 }
1217                 out.endTag(null, TAG_TAG);
1218             }
1219         }
1220 
1221         void deserialize(TypedXmlPullParser parser, long beginTimeMillis) throws Exception {
1222             int outerDepth = parser.getDepth();
1223             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
1224                 if (TAG_TAG.equals(parser.getName())) {
1225                     String attributionTag = parser.getAttributeValue(null, ATTR_TAG);
1226                     List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList(
1227                             attributionTag);
1228                     int innerDepth = parser.getDepth();
1229                     while (XmlUtils.nextElementWithin(parser, innerDepth)) {
1230                         if (TAG_ENTRY.equals(parser.getName())) {
1231                             long noteTime = parser.getAttributeLong(null, ATTR_NOTE_TIME);
1232                             long noteDuration = parser.getAttributeLong(null, ATTR_NOTE_DURATION,
1233                                     -1);
1234                             int uidState = parser.getAttributeInt(null, ATTR_UID_STATE);
1235                             int opFlags = parser.getAttributeInt(null, ATTR_FLAGS);
1236                             int attributionFlags = parser.getAttributeInt(null,
1237                                     ATTR_ATTRIBUTION_FLAGS, AppOpsManager.ATTRIBUTION_FLAGS_NONE);
1238                             int attributionChainId = parser.getAttributeInt(null, ATTR_CHAIN_ID,
1239                                     AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
1240                             if (noteTime + noteDuration < beginTimeMillis) {
1241                                 continue;
1242                             }
1243                             DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration,
1244                                     uidState, opFlags, attributionFlags, attributionChainId);
1245                             events.add(event);
1246                         }
1247                     }
1248                     Collections.sort(events, (a, b) -> a.mNoteTime < b.mNoteTime ? -1
1249                             : (a.mNoteTime == b.mNoteTime ? 0 : 1));
1250                 }
1251             }
1252         }
1253     }
1254 
1255     private final class DiscreteOpEvent {
1256         final long mNoteTime;
1257         final long mNoteDuration;
1258         final @AppOpsManager.UidState int mUidState;
1259         final @AppOpsManager.OpFlags int mOpFlag;
1260         final @AppOpsManager.AttributionFlags int mAttributionFlags;
1261         final int mAttributionChainId;
1262 
1263         DiscreteOpEvent(long noteTime, long noteDuration, @AppOpsManager.UidState int uidState,
1264                 @AppOpsManager.OpFlags int opFlag,
1265                 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
1266             mNoteTime = noteTime;
1267             mNoteDuration = noteDuration;
1268             mUidState = uidState;
1269             mOpFlag = opFlag;
1270             mAttributionFlags = attributionFlags;
1271             mAttributionChainId = attributionChainId;
1272         }
1273 
1274         public boolean equalsExceptDuration(DiscreteOpEvent o) {
1275             return mNoteTime == o.mNoteTime && mUidState == o.mUidState && mOpFlag == o.mOpFlag
1276                     && mAttributionFlags == o.mAttributionFlags
1277                     && mAttributionChainId == o.mAttributionChainId;
1278 
1279         }
1280 
1281         private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf,
1282                 @NonNull Date date, @NonNull String prefix) {
1283             pw.print(prefix);
1284             pw.print("Access [");
1285             pw.print(getUidStateName(mUidState));
1286             pw.print("-");
1287             pw.print(flagsToString(mOpFlag));
1288             pw.print("] at ");
1289             date.setTime(discretizeTimeStamp(mNoteTime));
1290             pw.print(sdf.format(date));
1291             if (mNoteDuration != -1) {
1292                 pw.print(" for ");
1293                 pw.print(discretizeDuration(mNoteDuration));
1294                 pw.print(" milliseconds ");
1295             }
1296             if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) {
1297                 pw.print(" attribution flags=");
1298                 pw.print(mAttributionFlags);
1299                 pw.print(" with chainId=");
1300                 pw.print(mAttributionChainId);
1301             }
1302             pw.println();
1303         }
1304 
1305         private void serialize(TypedXmlSerializer out) throws Exception {
1306             out.attributeLong(null, ATTR_NOTE_TIME, mNoteTime);
1307             if (mNoteDuration != -1) {
1308                 out.attributeLong(null, ATTR_NOTE_DURATION, mNoteDuration);
1309             }
1310             if (mAttributionFlags != AppOpsManager.ATTRIBUTION_FLAGS_NONE) {
1311                 out.attributeInt(null, ATTR_ATTRIBUTION_FLAGS, mAttributionFlags);
1312             }
1313             if (mAttributionChainId != AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE) {
1314                 out.attributeInt(null, ATTR_CHAIN_ID, mAttributionChainId);
1315             }
1316             out.attributeInt(null, ATTR_UID_STATE, mUidState);
1317             out.attributeInt(null, ATTR_FLAGS, mOpFlag);
1318         }
1319     }
1320 
1321     private static int[] parseOpsList(String opsList) {
1322         String[] strArr;
1323         if (opsList.isEmpty()) {
1324             strArr = new String[0];
1325         } else {
1326             strArr = opsList.split(",");
1327         }
1328         int nOps = strArr.length;
1329         int[] result = new int[nOps];
1330         try {
1331             for (int i = 0; i < nOps; i++) {
1332                 result[i] = Integer.parseInt(strArr[i]);
1333             }
1334         } catch (NumberFormatException e) {
1335             Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage());
1336             return parseOpsList(DEFAULT_DISCRETE_OPS);
1337         }
1338         return result;
1339     }
1340 
1341     private static List<DiscreteOpEvent> stableListMerge(List<DiscreteOpEvent> a,
1342             List<DiscreteOpEvent> b) {
1343         int nA = a.size();
1344         int nB = b.size();
1345         int i = 0;
1346         int k = 0;
1347         List<DiscreteOpEvent> result = new ArrayList<>(nA + nB);
1348         while (i < nA || k < nB) {
1349             if (i == nA) {
1350                 result.add(b.get(k++));
1351             } else if (k == nB) {
1352                 result.add(a.get(i++));
1353             } else if (a.get(i).mNoteTime < b.get(k).mNoteTime) {
1354                 result.add(a.get(i++));
1355             } else {
1356                 result.add(b.get(k++));
1357             }
1358         }
1359         return result;
1360     }
1361 
1362     private static List<DiscreteOpEvent> filterEventsList(List<DiscreteOpEvent> list,
1363             long beginTimeMillis, long endTimeMillis, @AppOpsManager.OpFlags int flagsFilter,
1364             int currentUid, String currentPackageName, int currentOp, String currentAttrTag,
1365             ArrayMap<Integer, AttributionChain> attributionChains) {
1366         int n = list.size();
1367         List<DiscreteOpEvent> result = new ArrayList<>(n);
1368         for (int i = 0; i < n; i++) {
1369             DiscreteOpEvent event = list.get(i);
1370             AttributionChain chain = attributionChains.get(event.mAttributionChainId);
1371             // If we have an attribution chain, and this event isn't the beginning node, remove it
1372             if (chain != null && !chain.isStart(currentPackageName, currentUid, currentAttrTag,
1373                     currentOp, event) && chain.isComplete()
1374                     && event.mAttributionChainId != ATTRIBUTION_CHAIN_ID_NONE) {
1375                 continue;
1376             }
1377             if ((event.mOpFlag & flagsFilter) != 0
1378                     && event.mNoteTime + event.mNoteDuration > beginTimeMillis
1379                     && event.mNoteTime < endTimeMillis) {
1380                 result.add(event);
1381             }
1382         }
1383         return result;
1384     }
1385 
1386     private static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) {
1387         if (!ArrayUtils.contains(sDiscreteOps, op)) {
1388             return false;
1389         }
1390         if ((flags & (sDiscreteFlags)) == 0) {
1391             return false;
1392         }
1393         return true;
1394     }
1395 
1396     private static long discretizeTimeStamp(long timeStamp) {
1397         return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
1398 
1399     }
1400 
1401     private static long discretizeDuration(long duration) {
1402         return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1)
1403                         / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
1404     }
1405 
1406     void setDebugMode(boolean debugMode) {
1407         this.mDebugMode = debugMode;
1408     }
1409 }
1410 
1411