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