• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 package com.android.server.appop;
17 
18 import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
19 import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
20 import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
21 import static android.app.AppOpsManager.FILTER_BY_UID;
22 import static android.app.AppOpsManager.HISTORY_FLAG_AGGREGATE;
23 import static android.app.AppOpsManager.HISTORY_FLAG_DISCRETE;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.AppOpsManager;
28 import android.app.AppOpsManager.HistoricalMode;
29 import android.app.AppOpsManager.HistoricalOp;
30 import android.app.AppOpsManager.HistoricalOps;
31 import android.app.AppOpsManager.HistoricalOpsRequestFilter;
32 import android.app.AppOpsManager.HistoricalPackageOps;
33 import android.app.AppOpsManager.HistoricalUidOps;
34 import android.app.AppOpsManager.OpFlags;
35 import android.app.AppOpsManager.OpHistoryFlags;
36 import android.app.AppOpsManager.UidState;
37 import android.content.ContentResolver;
38 import android.content.Context;
39 import android.database.ContentObserver;
40 import android.net.Uri;
41 import android.os.Build;
42 import android.os.Bundle;
43 import android.os.Debug;
44 import android.os.Environment;
45 import android.os.Message;
46 import android.os.Process;
47 import android.os.RemoteCallback;
48 import android.os.UserHandle;
49 import android.permission.flags.Flags;
50 import android.provider.Settings;
51 import android.util.ArraySet;
52 import android.util.LongSparseArray;
53 import android.util.Slog;
54 import android.util.TimeUtils;
55 import android.util.Xml;
56 
57 import com.android.internal.annotations.GuardedBy;
58 import com.android.internal.os.AtomicDirectory;
59 import com.android.internal.util.ArrayUtils;
60 import com.android.internal.util.XmlUtils;
61 import com.android.internal.util.function.pooled.PooledLambda;
62 import com.android.modules.utils.TypedXmlPullParser;
63 import com.android.modules.utils.TypedXmlSerializer;
64 import com.android.server.FgThread;
65 import com.android.server.IoThread;
66 
67 import org.xmlpull.v1.XmlPullParserException;
68 
69 import java.io.File;
70 import java.io.FileInputStream;
71 import java.io.FileNotFoundException;
72 import java.io.FileOutputStream;
73 import java.io.IOException;
74 import java.io.PrintWriter;
75 import java.nio.file.Files;
76 import java.text.SimpleDateFormat;
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.Collections;
80 import java.util.Date;
81 import java.util.LinkedList;
82 import java.util.List;
83 import java.util.Objects;
84 import java.util.Set;
85 import java.util.concurrent.TimeUnit;
86 
87 /**
88  * This class manages historical app op state. This includes reading, persistence,
89  * accounting, querying.
90  * <p>
91  * The history is kept forever in multiple files. Each file time contains the
92  * relative offset from the current time which time is encoded in the file name.
93  * The files contain historical app op state snapshots which have times that
94  * are relative to the time of the container file.
95  *
96  * The data in the files are stored in a logarithmic fashion where where every
97  * subsequent file would contain data for ten times longer interval with ten
98  * times more time distance between snapshots. Hence, the more time passes
99  * the lesser the fidelity.
100  * <p>
101  * For example, the first file would contain data for 1 days with snapshots
102  * every 0.1 days, the next file would contain data for the period 1 to 10
103  * days with snapshots every 1 days, and so on.
104  * <p>
105  * THREADING AND LOCKING: Reported ops must be processed as quickly as possible.
106  * We keep ops pending to be persisted in memory and write to disk on a background
107  * thread. Hence, methods that report op changes are locking only the in memory
108  * state guarded by the mInMemoryLock which happens to be the app ops service lock
109  * avoiding a lock addition on the critical path. When a query comes we need to
110  * evaluate it based off both in memory and on disk state. This means they need to
111  * be frozen with respect to each other and not change from the querying caller's
112  * perspective. To achieve this we add a dedicated mOnDiskLock to guard the on
113  * disk state. To have fast critical path we need to limit the locking of the
114  * mInMemoryLock, thus for operations that touch in memory and on disk state one
115  * must grab first the mOnDiskLock and then the mInMemoryLock and limit the
116  * in memory lock to extraction of relevant data. Locking order is critical to
117  * avoid deadlocks. The convention is that xxxDLocked suffix means the method
118  * must be called with the mOnDiskLock lock, xxxMLocked suffix means the method
119  * must be called with the mInMemoryLock, xxxDMLocked suffix means the method
120  * must be called with the mOnDiskLock and mInMemoryLock locks acquired in that
121  * exact order.
122  * <p>
123  * INITIALIZATION: We can initialize persistence only after the system is ready
124  * as we need to check the optional configuration override from the settings
125  * database which is not initialized at the time the app ops service is created.
126  * This means that all entry points that touch persistence should be short
127  * circuited via isPersistenceInitialized() check.
128  */
129 // TODO (bug:122218838): Make sure we handle start of epoch time
130 // TODO (bug:122218838): Validate changed time is handled correctly
131 final class LegacyHistoricalRegistry implements HistoricalRegistryInterface {
132     private static final boolean DEBUG = false;
133     private static final boolean KEEP_WTF_LOG = Build.IS_DEBUGGABLE;
134 
135     private static final String LOG_TAG = LegacyHistoricalRegistry.class.getSimpleName();
136 
137     private static final String PARAMETER_DELIMITER = ",";
138     private static final String PARAMETER_ASSIGNMENT = "=";
139 
140     private volatile @NonNull DiscreteOpsRegistry mDiscreteRegistry;
141 
142     @GuardedBy("mLock")
143     private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
144 
145     // Lock for read/write access to on disk state
146     private final Object mOnDiskLock = new Object();
147 
148     //Lock for read/write access to in memory state
149     private final @NonNull Object mInMemoryLock;
150 
151     private static final int MSG_WRITE_PENDING_HISTORY = 1;
152 
153     // See mMode
154     private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
155 
156     // See mBaseSnapshotInterval
157     private static final long DEFAULT_SNAPSHOT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15);
158 
159     // See mIntervalCompressionMultiplier
160     private static final long DEFAULT_COMPRESSION_STEP = 10;
161 
162     private static final String HISTORY_FILE_SUFFIX = ".xml";
163 
164     /**
165      * Whether history is enabled.
166      */
167     @GuardedBy("mInMemoryLock")
168     private int mMode = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
169 
170     /**
171      * This granularity has been chosen to allow clean delineation for intervals
172      * humans understand, 15 min, 60, min, a day, a week, a month (30 days).
173      */
174     @GuardedBy("mInMemoryLock")
175     private long mBaseSnapshotInterval = DEFAULT_SNAPSHOT_INTERVAL_MILLIS;
176 
177     /**
178      * The compression between steps. Each subsequent step is this much longer
179      * in terms of duration and each snapshot is this much more apart from the
180      * previous step.
181      */
182     @GuardedBy("mInMemoryLock")
183     private long mIntervalCompressionMultiplier = DEFAULT_COMPRESSION_STEP;
184 
185     // The current ops to which to add statistics.
186     @GuardedBy("mInMemoryLock")
187     private @Nullable HistoricalOps mCurrentHistoricalOps;
188 
189     // The time we should write the next snapshot.
190     @GuardedBy("mInMemoryLock")
191     private long mNextPersistDueTimeMillis;
192 
193     // How much to offset the history on the next write.
194     @GuardedBy("mInMemoryLock")
195     private long mPendingHistoryOffsetMillis;
196 
197     // Object managing persistence (read/write)
198     @GuardedBy("mOnDiskLock")
199     private Persistence mPersistence;
200 
201     private final Context mContext;
202 
LegacyHistoricalRegistry(@onNull Object lock, Context context)203     LegacyHistoricalRegistry(@NonNull Object lock, Context context) {
204         mInMemoryLock = lock;
205         mContext = context;
206         if (Flags.enableSqliteAppopsAccesses()) {
207             mDiscreteRegistry = new DiscreteOpsSqlRegistry(context);
208         } else {
209             mDiscreteRegistry = new DiscreteOpsXmlRegistry(lock);
210         }
211     }
212 
LegacyHistoricalRegistry(@onNull LegacyHistoricalRegistry other)213     LegacyHistoricalRegistry(@NonNull LegacyHistoricalRegistry other) {
214         this(other.mInMemoryLock, other.mContext);
215         mMode = other.mMode;
216         mBaseSnapshotInterval = other.mBaseSnapshotInterval;
217         mIntervalCompressionMultiplier = other.mIntervalCompressionMultiplier;
218         mDiscreteRegistry = other.mDiscreteRegistry;
219     }
220 
221     @Override
systemReady(@onNull ContentResolver resolver)222     public void systemReady(@NonNull ContentResolver resolver) {
223         mDiscreteRegistry.systemReady();
224         final Uri uri = Settings.Global.getUriFor(Settings.Global.APPOP_HISTORY_PARAMETERS);
225         resolver.registerContentObserver(uri, false, new ContentObserver(
226                 FgThread.getHandler()) {
227             @Override
228             public void onChange(boolean selfChange) {
229                 updateParametersFromSetting(resolver);
230             }
231         });
232 
233         updateParametersFromSetting(resolver);
234 
235         synchronized (mOnDiskLock) {
236             synchronized (mInMemoryLock) {
237                 if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) {
238                     // Can be uninitialized if there is no config in the settings table.
239                     if (!isPersistenceInitializedMLocked()) {
240                         mPersistence = new Persistence(mBaseSnapshotInterval,
241                                 mIntervalCompressionMultiplier);
242                     }
243 
244                     // When starting always adjust history to now.
245                     final long lastPersistTimeMills =
246                             mPersistence.getLastPersistTimeMillisDLocked();
247 
248                     if (lastPersistTimeMills > 0) {
249                         mPendingHistoryOffsetMillis = System.currentTimeMillis()
250                                 - lastPersistTimeMills;
251 
252                         if (DEBUG) {
253                             Slog.i(LOG_TAG, "Time since last write: "
254                                     + TimeUtils.formatDuration(mPendingHistoryOffsetMillis)
255                                     + " by which to push history on next write");
256                         }
257                     }
258                 }
259             }
260         }
261         if (Flags.enableSqliteAppopsAccesses()) {
262             if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) {
263                 DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry;
264                 DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mContext);
265                 DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
266             }
267         } else {
268             if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite
269                 DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext);
270                 DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry;
271                 DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
272             }
273         }
274     }
275 
isPersistenceInitializedMLocked()276     private boolean isPersistenceInitializedMLocked() {
277         return mPersistence != null;
278     }
279 
updateParametersFromSetting(@onNull ContentResolver resolver)280     private void updateParametersFromSetting(@NonNull ContentResolver resolver) {
281         final String setting = Settings.Global.getString(resolver,
282                 Settings.Global.APPOP_HISTORY_PARAMETERS);
283         if (setting == null) {
284             return;
285         }
286         String modeValue = null;
287         String baseSnapshotIntervalValue = null;
288         String intervalMultiplierValue = null;
289         final String[] parameters = setting.split(PARAMETER_DELIMITER);
290         for (String parameter : parameters) {
291             final String[] parts = parameter.split(PARAMETER_ASSIGNMENT);
292             if (parts.length == 2) {
293                 final String key = parts[0].trim();
294                 switch (key) {
295                     case Settings.Global.APPOP_HISTORY_MODE: {
296                         modeValue = parts[1].trim();
297                     } break;
298                     case Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS: {
299                         baseSnapshotIntervalValue = parts[1].trim();
300                     } break;
301                     case Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER: {
302                         intervalMultiplierValue = parts[1].trim();
303                     } break;
304                     default: {
305                         Slog.w(LOG_TAG, "Unknown parameter: " + parameter);
306                     }
307                 }
308             }
309         }
310         if (modeValue != null && baseSnapshotIntervalValue != null
311                 && intervalMultiplierValue != null) {
312             try {
313                 final int mode = AppOpsManager.parseHistoricalMode(modeValue);
314                 final long baseSnapshotInterval = Long.parseLong(baseSnapshotIntervalValue);
315                 final int intervalCompressionMultiplier = Integer.parseInt(intervalMultiplierValue);
316                 setHistoryParameters(mode, baseSnapshotInterval, intervalCompressionMultiplier);
317                 return;
318             } catch (NumberFormatException ignored) { }
319         }
320         Slog.w(LOG_TAG, "Bad value for" + Settings.Global.APPOP_HISTORY_PARAMETERS
321                 + "=" + setting + " resetting!");
322     }
323 
324 
325     @Override
dump(String prefix, PrintWriter pw, int filterUid, @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp, @HistoricalOpsRequestFilter int filter)326     public void dump(String prefix, PrintWriter pw, int filterUid,
327             @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp,
328             @HistoricalOpsRequestFilter int filter) {
329         synchronized (mOnDiskLock) {
330             synchronized (mInMemoryLock) {
331                 pw.println();
332                 pw.print(prefix);
333                 pw.print("History:");
334 
335                 pw.print("  mode=");
336                 pw.println(AppOpsManager.historicalModeToString(mMode));
337 
338                 final StringDumpVisitor visitor = new StringDumpVisitor(prefix + "  ",
339                         pw, filterUid, filterPackage, filterAttributionTag, filterOp, filter);
340                 final long nowMillis = System.currentTimeMillis();
341 
342                 // Dump in memory state first
343                 final HistoricalOps currentOps = getUpdatedPendingHistoricalOpsMLocked(
344                         nowMillis);
345                 makeRelativeToEpochStart(currentOps, nowMillis);
346                 currentOps.accept(visitor);
347 
348                 if (!isPersistenceInitializedMLocked()) {
349                     Slog.e(LOG_TAG, "Interaction before persistence initialized");
350                     return;
351                 }
352 
353                 final List<HistoricalOps> ops = mPersistence.readHistoryDLocked();
354                 if (ops != null) {
355                     // TODO (bug:122218838): Make sure this is properly dumped
356                     final long remainingToFillBatchMillis = mNextPersistDueTimeMillis
357                             - nowMillis - mBaseSnapshotInterval;
358                     final int opCount = ops.size();
359                     for (int i = 0; i < opCount; i++) {
360                         final HistoricalOps op = ops.get(i);
361                         op.offsetBeginAndEndTime(remainingToFillBatchMillis);
362                         makeRelativeToEpochStart(op, nowMillis);
363                         op.accept(visitor);
364                     }
365                 } else {
366                     pw.println("  Empty");
367                 }
368             }
369         }
370     }
371 
372     @Override
dumpDiscreteData(@onNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, @Nullable String attributionTagFilter, @HistoricalOpsRequestFilter int filter, int dumpOp, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)373     public void dumpDiscreteData(@NonNull PrintWriter pw, int uidFilter,
374             @Nullable String packageNameFilter, @Nullable String attributionTagFilter,
375             @HistoricalOpsRequestFilter int filter, int dumpOp,
376             @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
377             int nDiscreteOps) {
378         mDiscreteRegistry.dump(pw, uidFilter, packageNameFilter, attributionTagFilter, filter,
379                 dumpOp, sdf, date, prefix, nDiscreteOps);
380     }
381 
getMode()382     @HistoricalMode int getMode() {
383         synchronized (mInMemoryLock) {
384             return mMode;
385         }
386     }
387 
388     @Override
getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String[] opNames, @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis, @OpFlags int flags, String[] attributionExemptedPackages, @NonNull RemoteCallback callback)389     public void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName,
390             @Nullable String attributionTag, @Nullable String[] opNames,
391             @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter,
392             long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
393             String[] attributionExemptedPackages, @NonNull RemoteCallback callback) {
394         final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
395 
396         if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) {
397             synchronized (mOnDiskLock) {
398                 synchronized (mInMemoryLock) {
399                     if (!isPersistenceInitializedMLocked()) {
400                         Slog.e(LOG_TAG, "Interaction before persistence initialized");
401                         callback.sendResult(new Bundle());
402                         return;
403                     }
404                 }
405                 mPersistence.collectHistoricalOpsDLocked(result, uid, packageName,
406                         attributionTag,
407                         opNames, filter, beginTimeMillis, endTimeMillis, flags);
408             }
409         }
410 
411         if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) {
412             mDiscreteRegistry.addFilteredDiscreteOpsToHistoricalOps(result, beginTimeMillis,
413                     endTimeMillis, filter, uid, packageName, opNames, attributionTag,
414                     flags, new ArraySet<>(attributionExemptedPackages));
415         }
416 
417         final Bundle payload = new Bundle();
418         payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
419         callback.sendResult(payload);
420     }
421 
422     @Override
getHistoricalOps(int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String[] opNames, @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis, @OpFlags int flags, @Nullable String[] attributionExemptPkgs, @NonNull RemoteCallback callback)423     public void getHistoricalOps(int uid, @Nullable String packageName,
424             @Nullable String attributionTag, @Nullable String[] opNames,
425             @OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter,
426             long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
427             @Nullable String[] attributionExemptPkgs, @NonNull RemoteCallback callback) {
428         final long currentTimeMillis = System.currentTimeMillis();
429         if (endTimeMillis == Long.MAX_VALUE) {
430             endTimeMillis = currentTimeMillis;
431         }
432 
433         final Bundle payload = new Bundle();
434 
435         // Argument times are based off epoch start while our internal store is
436         // based off now, so take this into account.
437         final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0);
438         final long inMemoryAdjEndTimeMillis = Math.max(currentTimeMillis - beginTimeMillis, 0);
439         final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis,
440                 inMemoryAdjEndTimeMillis);
441 
442         if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) {
443             mDiscreteRegistry.addFilteredDiscreteOpsToHistoricalOps(result, beginTimeMillis,
444                     endTimeMillis, filter, uid, packageName, opNames, attributionTag, flags,
445                     new ArraySet<>(attributionExemptPkgs));
446         }
447 
448         if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) {
449             synchronized (mOnDiskLock) {
450                 final List<HistoricalOps> pendingWrites;
451                 final HistoricalOps currentOps;
452                 boolean collectOpsFromDisk;
453 
454                 synchronized (mInMemoryLock) {
455                     if (!isPersistenceInitializedMLocked()) {
456                         Slog.e(LOG_TAG, "Interaction before persistence initialized");
457                         callback.sendResult(new Bundle());
458                         return;
459                     }
460 
461                     currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis);
462                     if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis()
463                             || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
464                         // Some of the current batch falls into the query, so extract that.
465                         final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
466                         currentOpsCopy.filter(uid, packageName, attributionTag, opNames,
467                                 historyFlags, filter, inMemoryAdjBeginTimeMillis,
468                                 inMemoryAdjEndTimeMillis);
469                         result.merge(currentOpsCopy);
470                     }
471                     pendingWrites = new ArrayList<>(mPendingWrites);
472                     mPendingWrites.clear();
473                     collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis();
474                 }
475 
476                 // If the query was only for in-memory state - done.
477                 if (collectOpsFromDisk) {
478                     // If there is a write in flight we need to force it now
479                     persistPendingHistory(pendingWrites);
480                     // Collect persisted state.
481                     final long onDiskAndInMemoryOffsetMillis = currentTimeMillis
482                             - mNextPersistDueTimeMillis + mBaseSnapshotInterval;
483                     final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis
484                             - onDiskAndInMemoryOffsetMillis, 0);
485                     final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
486                             - onDiskAndInMemoryOffsetMillis, 0);
487                     mPersistence.collectHistoricalOpsDLocked(result, uid, packageName,
488                             attributionTag,
489                             opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis,
490                             flags);
491                 }
492             }
493         }
494         // Rebase the result time to be since epoch.
495         result.setBeginAndEndTime(beginTimeMillis, endTimeMillis);
496 
497         // Send back the result.
498         payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
499         callback.sendResult(payload);
500     }
501 
502     @Override
incrementOpAccessedCount(int op, int uid, @NonNull String packageName, @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long accessTime, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, int accessCount)503     public void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
504             @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
505             @OpFlags int flags, long accessTime,
506             @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
507             int accessCount) {
508         synchronized (mInMemoryLock) {
509             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
510                 if (!isPersistenceInitializedMLocked()) {
511                     Slog.v(LOG_TAG, "Interaction before persistence initialized");
512                     return;
513                 }
514                 getUpdatedPendingHistoricalOpsMLocked(
515                         System.currentTimeMillis()).increaseAccessCount(op, uid, packageName,
516                         attributionTag, uidState, flags, accessCount);
517 
518                 mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
519                         attributionTag, flags, uidState, accessTime, -1, attributionFlags,
520                         attributionChainId);
521             }
522         }
523     }
524 
525     @Override
incrementOpRejectedCount(int op, int uid, @NonNull String packageName, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags)526     public void incrementOpRejectedCount(int op, int uid, @NonNull String packageName,
527             @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags) {
528         synchronized (mInMemoryLock) {
529             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
530                 if (!isPersistenceInitializedMLocked()) {
531                     Slog.v(LOG_TAG, "Interaction before persistence initialized");
532                     return;
533                 }
534                 getUpdatedPendingHistoricalOpsMLocked(
535                         System.currentTimeMillis()).increaseRejectCount(op, uid, packageName,
536                         attributionTag, uidState, flags, 1);
537             }
538         }
539     }
540 
541     @Override
increaseOpAccessDuration(int op, int uid, @NonNull String packageName, @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long eventStartTime, long increment, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)542     public void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
543             @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
544             @OpFlags int flags, long eventStartTime, long increment,
545             @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) {
546         synchronized (mInMemoryLock) {
547             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
548                 if (!isPersistenceInitializedMLocked()) {
549                     Slog.v(LOG_TAG, "Interaction before persistence initialized");
550                     return;
551                 }
552                 getUpdatedPendingHistoricalOpsMLocked(
553                         System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName,
554                         attributionTag, uidState, flags, increment);
555                 mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op,
556                         attributionTag, flags, uidState, eventStartTime, increment,
557                         attributionFlags, attributionChainId);
558             }
559         }
560     }
561 
562     @Override
setHistoryParameters(@istoricalMode int mode, long baseSnapshotInterval, long intervalCompressionMultiplier)563     public void setHistoryParameters(@HistoricalMode int mode,
564             long baseSnapshotInterval, long intervalCompressionMultiplier) {
565         synchronized (mOnDiskLock) {
566             synchronized (mInMemoryLock) {
567                 // NOTE: We allow this call if persistence is not initialized as
568                 // it is a part of the persistence initialization process.
569                 boolean resampleHistory = false;
570                 Slog.i(LOG_TAG, "New history parameters: mode:"
571                         + AppOpsManager.historicalModeToString(mode) + " baseSnapshotInterval:"
572                         + baseSnapshotInterval + " intervalCompressionMultiplier:"
573                         + intervalCompressionMultiplier);
574                 if (mMode != mode) {
575                     mMode = mode;
576                     if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) {
577                         clearHistoryOnDiskDLocked();
578                     }
579                     if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE) {
580                         mDiscreteRegistry.setDebugMode(true);
581                     }
582                 }
583                 if (mBaseSnapshotInterval != baseSnapshotInterval) {
584                     mBaseSnapshotInterval = baseSnapshotInterval;
585                     resampleHistory = true;
586                 }
587                 if (mIntervalCompressionMultiplier != intervalCompressionMultiplier) {
588                     mIntervalCompressionMultiplier = intervalCompressionMultiplier;
589                     resampleHistory = true;
590                 }
591                 if (resampleHistory) {
592                     resampleHistoryOnDiskInMemoryDMLocked(0);
593                 }
594             }
595         }
596     }
597 
598     @Override
offsetHistory(long offsetMillis)599     public void offsetHistory(long offsetMillis) {
600         synchronized (mOnDiskLock) {
601             synchronized (mInMemoryLock) {
602                 if (!isPersistenceInitializedMLocked()) {
603                     Slog.e(LOG_TAG, "Interaction before persistence initialized");
604                     return;
605                 }
606             }
607             final List<HistoricalOps> history = mPersistence.readHistoryDLocked();
608             clearHistoricalRegistry();
609             if (history != null) {
610                 final int historySize = history.size();
611                 for (int i = 0; i < historySize; i++) {
612                     final HistoricalOps ops = history.get(i);
613                     ops.offsetBeginAndEndTime(offsetMillis);
614                 }
615                 if (offsetMillis < 0) {
616                     pruneFutureOps(history);
617                 }
618                 mPersistence.persistHistoricalOpsDLocked(history);
619             }
620         }
621         mDiscreteRegistry.offsetHistory(offsetMillis);
622     }
623 
624     @Override
addHistoricalOps(HistoricalOps ops)625     public void addHistoricalOps(HistoricalOps ops) {
626         final List<HistoricalOps> pendingWrites;
627         synchronized (mInMemoryLock) {
628             if (!isPersistenceInitializedMLocked()) {
629                 Slog.d(LOG_TAG, "Interaction before persistence initialized");
630                 return;
631             }
632             // The history files start from mBaseSnapshotInterval - take this into account.
633             ops.offsetBeginAndEndTime(mBaseSnapshotInterval);
634             mPendingWrites.offerFirst(ops);
635             pendingWrites = new ArrayList<>(mPendingWrites);
636             mPendingWrites.clear();
637         }
638         persistPendingHistory(pendingWrites);
639     }
640 
resampleHistoryOnDiskInMemoryDMLocked(long offsetMillis)641     private void resampleHistoryOnDiskInMemoryDMLocked(long offsetMillis) {
642         mPersistence = new Persistence(mBaseSnapshotInterval, mIntervalCompressionMultiplier);
643         offsetHistory(offsetMillis);
644     }
645 
646     @Override
resetHistoryParameters()647     public void resetHistoryParameters() {
648         if (!isPersistenceInitializedMLocked()) {
649             Slog.d(LOG_TAG, "Interaction before persistence initialized");
650             return;
651         }
652         setHistoryParameters(DEFAULT_MODE, DEFAULT_SNAPSHOT_INTERVAL_MILLIS,
653                 DEFAULT_COMPRESSION_STEP);
654         mDiscreteRegistry.setDebugMode(false);
655     }
656 
657     @Override
clearHistory(int uid, String packageName)658     public void clearHistory(int uid, String packageName) {
659         synchronized (mOnDiskLock) {
660             synchronized (mInMemoryLock) {
661                 if (!isPersistenceInitializedMLocked()) {
662                     Slog.d(LOG_TAG, "Interaction before persistence initialized");
663                     return;
664                 }
665                 if (mMode != AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
666                     return;
667                 }
668 
669                 for (int index = 0; index < mPendingWrites.size(); index++) {
670                     mPendingWrites.get(index).clearHistory(uid, packageName);
671                 }
672 
673                 getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
674                         .clearHistory(uid, packageName);
675 
676                 mPersistence.clearHistoryDLocked(uid, packageName);
677             }
678         }
679         mDiscreteRegistry.clearHistory(uid, packageName);
680     }
681 
682     @Override
writeAndClearDiscreteHistory()683     public void writeAndClearDiscreteHistory() {
684         mDiscreteRegistry.writeAndClearOldAccessHistory();
685     }
686 
687     @Override
clearAllHistory()688     public void clearAllHistory() {
689         clearHistoricalRegistry();
690         mDiscreteRegistry.clearHistory();
691     }
692 
clearHistoricalRegistry()693     void clearHistoricalRegistry() {
694         synchronized (mOnDiskLock) {
695             synchronized (mInMemoryLock) {
696                 if (!isPersistenceInitializedMLocked()) {
697                     Slog.d(LOG_TAG, "Interaction before persistence initialized");
698                     return;
699                 }
700                 clearHistoryOnDiskDLocked();
701                 mNextPersistDueTimeMillis = 0;
702                 mPendingHistoryOffsetMillis = 0;
703                 mCurrentHistoricalOps = null;
704             }
705         }
706     }
707 
clearHistoryOnDiskDLocked()708     private void clearHistoryOnDiskDLocked() {
709         IoThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
710         synchronized (mInMemoryLock) {
711             mCurrentHistoricalOps = null;
712             mNextPersistDueTimeMillis = System.currentTimeMillis();
713             mPendingWrites.clear();
714         }
715         Persistence.clearHistoryDLocked();
716     }
717 
getUpdatedPendingHistoricalOpsMLocked(long now)718     private @NonNull HistoricalOps getUpdatedPendingHistoricalOpsMLocked(long now) {
719         if (mCurrentHistoricalOps != null) {
720             final long remainingTimeMillis = mNextPersistDueTimeMillis - now;
721             if (remainingTimeMillis > mBaseSnapshotInterval) {
722                 // If time went backwards we need to push history to the future with the
723                 // overflow over our snapshot interval. If time went forward do nothing
724                 // as we would naturally push history into the past on the next write.
725                 mPendingHistoryOffsetMillis = remainingTimeMillis - mBaseSnapshotInterval;
726             }
727             final long elapsedTimeMillis = mBaseSnapshotInterval - remainingTimeMillis;
728             mCurrentHistoricalOps.setEndTime(elapsedTimeMillis);
729             if (remainingTimeMillis > 0) {
730                 if (DEBUG) {
731                     Slog.i(LOG_TAG, "Returning current in-memory state");
732                 }
733                 return mCurrentHistoricalOps;
734             }
735             if (mCurrentHistoricalOps.isEmpty()) {
736                 mCurrentHistoricalOps.setBeginAndEndTime(0, 0);
737                 mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
738                 return mCurrentHistoricalOps;
739             }
740             // The current batch is full, so persist taking into account overdue persist time.
741             mCurrentHistoricalOps.offsetBeginAndEndTime(mBaseSnapshotInterval);
742             mCurrentHistoricalOps.setBeginTime(mCurrentHistoricalOps.getEndTimeMillis()
743                     - mBaseSnapshotInterval);
744             final long overdueTimeMillis = Math.abs(remainingTimeMillis);
745             mCurrentHistoricalOps.offsetBeginAndEndTime(overdueTimeMillis);
746             schedulePersistHistoricalOpsMLocked(mCurrentHistoricalOps);
747         }
748         // The current batch is in the future, i.e. not complete yet.
749         mCurrentHistoricalOps = new HistoricalOps(0, 0);
750         mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
751         if (DEBUG) {
752             Slog.i(LOG_TAG, "Returning new in-memory state");
753         }
754         return mCurrentHistoricalOps;
755     }
756 
757     @Override
shutdown()758     public void shutdown() {
759         synchronized (mInMemoryLock) {
760             if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) {
761                 return;
762             }
763         }
764         // Do not call persistPendingHistory inside the memory lock, due to possible deadlock
765         persistPendingHistory();
766         mDiscreteRegistry.shutdown();
767     }
768 
769     @Override
persistPendingHistory()770     public void persistPendingHistory() {
771         final List<HistoricalOps> pendingWrites;
772         synchronized (mOnDiskLock) {
773             synchronized (mInMemoryLock) {
774                 pendingWrites = new ArrayList<>(mPendingWrites);
775                 mPendingWrites.clear();
776                 if (mPendingHistoryOffsetMillis != 0) {
777                     resampleHistoryOnDiskInMemoryDMLocked(mPendingHistoryOffsetMillis);
778                     mPendingHistoryOffsetMillis = 0;
779                 }
780             }
781             persistPendingHistory(pendingWrites);
782         }
783         mDiscreteRegistry.writeAndClearOldAccessHistory();
784     }
785 
persistPendingHistory(@onNull List<HistoricalOps> pendingWrites)786     private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
787         synchronized (mOnDiskLock) {
788             IoThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
789             if (pendingWrites.isEmpty()) {
790                 return;
791             }
792             final int opCount = pendingWrites.size();
793             // Pending writes are offset relative to each other, so take this
794             // into account to persist everything in one shot - single write.
795             for (int i = 0; i < opCount; i++) {
796                 final HistoricalOps current = pendingWrites.get(i);
797                 if (i > 0) {
798                     final HistoricalOps previous = pendingWrites.get(i - 1);
799                     current.offsetBeginAndEndTime(previous.getBeginTimeMillis());
800                 }
801             }
802             mPersistence.persistHistoricalOpsDLocked(pendingWrites);
803         }
804     }
805 
schedulePersistHistoricalOpsMLocked(@onNull HistoricalOps ops)806     private void schedulePersistHistoricalOpsMLocked(@NonNull HistoricalOps ops) {
807         final Message message = PooledLambda.obtainMessage(
808                 LegacyHistoricalRegistry::persistPendingHistory, LegacyHistoricalRegistry.this);
809         message.what = MSG_WRITE_PENDING_HISTORY;
810         IoThread.getHandler().sendMessage(message);
811         mPendingWrites.offerFirst(ops);
812     }
813 
makeRelativeToEpochStart(@onNull HistoricalOps ops, long nowMillis)814     private static void makeRelativeToEpochStart(@NonNull HistoricalOps ops, long nowMillis) {
815         ops.setBeginAndEndTime(nowMillis - ops.getEndTimeMillis(),
816                 nowMillis - ops.getBeginTimeMillis());
817     }
818 
pruneFutureOps(@onNull List<HistoricalOps> ops)819     private void pruneFutureOps(@NonNull List<HistoricalOps> ops) {
820         final int opCount = ops.size();
821         for (int i = opCount - 1; i >= 0; i--) {
822             final HistoricalOps op = ops.get(i);
823             if (op.getEndTimeMillis() <= mBaseSnapshotInterval) {
824                 ops.remove(i);
825             } else if (op.getBeginTimeMillis() < mBaseSnapshotInterval) {
826                 final double filterScale = (double) (op.getEndTimeMillis() - mBaseSnapshotInterval)
827                         / (double) op.getDurationMillis();
828                 Persistence.spliceFromBeginning(op, filterScale);
829             }
830         }
831     }
832 
833     private static final class Persistence {
834         private static final boolean DEBUG = false;
835 
836         private static final String LOG_TAG = Persistence.class.getSimpleName();
837 
838         private static final String TAG_HISTORY = "history";
839         private static final String TAG_OPS = "ops";
840         private static final String TAG_UID = "uid";
841         private static final String TAG_PACKAGE = "pkg";
842         private static final String TAG_ATTRIBUTION = "ftr";
843         private static final String TAG_OP = "op";
844         private static final String TAG_STATE = "st";
845 
846         private static final String ATTR_VERSION = "ver";
847         private static final String ATTR_NAME = "na";
848         private static final String ATTR_ACCESS_COUNT = "ac";
849         private static final String ATTR_REJECT_COUNT = "rc";
850         private static final String ATTR_ACCESS_DURATION = "du";
851         private static final String ATTR_BEGIN_TIME = "beg";
852         private static final String ATTR_END_TIME = "end";
853         private static final String ATTR_OVERFLOW = "ov";
854 
855         private static final int CURRENT_VERSION = 2;
856 
857         private final long mBaseSnapshotInterval;
858         private final long mIntervalCompressionMultiplier;
859 
Persistence(long baseSnapshotInterval, long intervalCompressionMultiplier)860         Persistence(long baseSnapshotInterval, long intervalCompressionMultiplier) {
861             mBaseSnapshotInterval = baseSnapshotInterval;
862             mIntervalCompressionMultiplier = intervalCompressionMultiplier;
863         }
864 
865         private static final AtomicDirectory sHistoricalAppOpsDir = new AtomicDirectory(
866                 new File(new File(Environment.getDataSystemDirectory(), "appops"), "history"));
867 
generateFile(@onNull File baseDir, int depth)868         private File generateFile(@NonNull File baseDir, int depth) {
869             final long globalBeginMillis = computeGlobalIntervalBeginMillis(depth);
870             return new File(baseDir, Long.toString(globalBeginMillis) + HISTORY_FILE_SUFFIX);
871         }
872 
clearHistoryDLocked(int uid, String packageName)873         void clearHistoryDLocked(int uid, String packageName) {
874             List<HistoricalOps> historicalOps = readHistoryDLocked();
875 
876             if (historicalOps == null) {
877                 return;
878             }
879 
880             for (int index = 0; index < historicalOps.size(); index++) {
881                 historicalOps.get(index).clearHistory(uid, packageName);
882             }
883 
884             clearHistoryDLocked();
885 
886             persistHistoricalOpsDLocked(historicalOps);
887         }
888 
clearHistoryDLocked()889         static void clearHistoryDLocked() {
890             sHistoricalAppOpsDir.delete();
891         }
892 
persistHistoricalOpsDLocked(@onNull List<HistoricalOps> ops)893         void persistHistoricalOpsDLocked(@NonNull List<HistoricalOps> ops) {
894             if (DEBUG) {
895                 Slog.i(LOG_TAG, "Persisting ops:\n" + opsToDebugString(ops));
896                 enforceOpsWellFormed(ops);
897             }
898             try {
899                 final File newBaseDir = sHistoricalAppOpsDir.startWrite();
900                 final File oldBaseDir = sHistoricalAppOpsDir.getBackupDirectory();
901                 final HistoricalFilesInvariant filesInvariant;
902                 if (DEBUG) {
903                     filesInvariant = new HistoricalFilesInvariant();
904                     filesInvariant.startTracking(oldBaseDir);
905                 }
906                 final Set<String> oldFileNames = getHistoricalFileNames(oldBaseDir);
907                 handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops,
908                         oldFileNames,  0);
909                 if (DEBUG) {
910                     filesInvariant.stopTracking(newBaseDir);
911                 }
912                 sHistoricalAppOpsDir.finishWrite();
913             } catch (Throwable t) {
914                 wtf("Failed to write historical app ops, restoring backup", t, null);
915                 sHistoricalAppOpsDir.failWrite();
916             }
917         }
918 
readHistoryRawDLocked()919         @Nullable List<HistoricalOps> readHistoryRawDLocked() {
920             return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/,
921                     null /*filterPackageName*/, null /*filterAttributionTag*/,
922                     null /*filterOpNames*/, 0 /*filter*/, 0 /*filterBeginTimeMills*/,
923                     Long.MAX_VALUE /*filterEndTimeMills*/, AppOpsManager.OP_FLAGS_ALL);
924         }
925 
readHistoryDLocked()926         @Nullable List<HistoricalOps> readHistoryDLocked() {
927             final List<HistoricalOps> result = readHistoryRawDLocked();
928             // Take into account in memory state duration.
929             if (result != null) {
930                 final int opCount = result.size();
931                 for (int i = 0; i < opCount; i++) {
932                     result.get(i).offsetBeginAndEndTime(mBaseSnapshotInterval);
933                 }
934             }
935             return result;
936         }
937 
getLastPersistTimeMillisDLocked()938         long getLastPersistTimeMillisDLocked() {
939             File baseDir = null;
940             try {
941                 baseDir = sHistoricalAppOpsDir.startRead();
942                 final File[] files = baseDir.listFiles();
943                 if (files != null && files.length > 0) {
944                     File shortestFile = null;
945                     for (File candidate : files) {
946                         final String candidateName = candidate.getName();
947                         if (!candidateName.endsWith(HISTORY_FILE_SUFFIX)) {
948                             continue;
949                         }
950                         if (shortestFile == null) {
951                             shortestFile = candidate;
952                         } else if (candidateName.length() < shortestFile.getName().length()) {
953                             shortestFile = candidate;
954                         }
955                     }
956                     if (shortestFile == null) {
957                         return 0;
958                     }
959                     return shortestFile.lastModified();
960                 }
961                 sHistoricalAppOpsDir.finishRead();
962             } catch (Throwable e) {
963                 wtf("Error reading historical app ops. Deleting history.", e, baseDir);
964                 sHistoricalAppOpsDir.delete();
965             }
966             return 0;
967         }
968 
collectHistoricalOpsDLocked(@onNull HistoricalOps currentOps, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeingMillis, long filterEndMillis, @OpFlags int filterFlags)969         private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps, int filterUid,
970                 @Nullable String filterPackageName, @Nullable String filterAttributionTag,
971                 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
972                 long filterBeingMillis, long filterEndMillis, @OpFlags int filterFlags) {
973             final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid,
974                     filterPackageName, filterAttributionTag, filterOpNames, filter,
975                     filterBeingMillis, filterEndMillis, filterFlags);
976             if (readOps != null) {
977                 final int readCount = readOps.size();
978                 for (int i = 0; i < readCount; i++) {
979                     final HistoricalOps readOp = readOps.get(i);
980                     currentOps.merge(readOp);
981                 }
982             }
983         }
984 
collectHistoricalOpsBaseDLocked(int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags)985         private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(int filterUid,
986                 @Nullable String filterPackageName, @Nullable String filterAttributionTag,
987                 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
988                 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags) {
989             File baseDir = null;
990             try {
991                 baseDir = sHistoricalAppOpsDir.startRead();
992                 final HistoricalFilesInvariant filesInvariant;
993                 if (DEBUG) {
994                     filesInvariant = new HistoricalFilesInvariant();
995                     filesInvariant.startTracking(baseDir);
996                 }
997                 final Set<String> historyFiles = getHistoricalFileNames(baseDir);
998                 final long[] globalContentOffsetMillis = {0};
999                 final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
1000                         baseDir, filterUid, filterPackageName, filterAttributionTag, filterOpNames,
1001                         filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
1002                         globalContentOffsetMillis, null /*outOps*/, 0 /*depth*/, historyFiles);
1003                 if (DEBUG) {
1004                     filesInvariant.stopTracking(baseDir);
1005                 }
1006                 sHistoricalAppOpsDir.finishRead();
1007                 return ops;
1008             } catch (Throwable t) {
1009                 wtf("Error reading historical app ops. Deleting history.", t, baseDir);
1010                 sHistoricalAppOpsDir.delete();
1011             }
1012             return null;
1013         }
1014 
collectHistoricalOpsRecursiveDLocked( @onNull File baseDir, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @NonNull long[] globalContentOffsetMillis, @Nullable LinkedList<HistoricalOps> outOps, int depth, @NonNull Set<String> historyFiles)1015         private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked(
1016                 @NonNull File baseDir, int filterUid, @Nullable String filterPackageName,
1017                 @Nullable String filterAttributionTag, @Nullable String[] filterOpNames,
1018                 @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
1019                 long filterEndTimeMillis, @OpFlags int filterFlags,
1020                 @NonNull long[] globalContentOffsetMillis,
1021                 @Nullable LinkedList<HistoricalOps> outOps, int depth,
1022                 @NonNull Set<String> historyFiles)
1023                 throws IOException, XmlPullParserException {
1024             final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
1025                     depth) * mBaseSnapshotInterval;
1026             final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
1027                     depth + 1) * mBaseSnapshotInterval;
1028 
1029             filterBeginTimeMillis = Math.max(filterBeginTimeMillis - previousIntervalEndMillis, 0);
1030             filterEndTimeMillis = filterEndTimeMillis - previousIntervalEndMillis;
1031 
1032             // Read historical data at this level
1033             final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir,
1034                     previousIntervalEndMillis, currentIntervalEndMillis, filterUid,
1035                     filterPackageName, filterAttributionTag, filterOpNames, filter,
1036                     filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
1037                     globalContentOffsetMillis, depth, historyFiles);
1038             // Empty is a special signal to stop diving
1039             if (readOps != null && readOps.isEmpty()) {
1040                 return outOps;
1041             }
1042 
1043             // Collect older historical data from subsequent levels
1044             outOps = collectHistoricalOpsRecursiveDLocked(baseDir, filterUid, filterPackageName,
1045                     filterAttributionTag, filterOpNames, filter, filterBeginTimeMillis,
1046                     filterEndTimeMillis, filterFlags, globalContentOffsetMillis, outOps, depth + 1,
1047                     historyFiles);
1048 
1049             // Make older historical data relative to the current historical level
1050             if (outOps != null) {
1051                 final int opCount = outOps.size();
1052                 for (int i = 0; i < opCount; i++) {
1053                     final HistoricalOps collectedOp = outOps.get(i);
1054                     collectedOp.offsetBeginAndEndTime(currentIntervalEndMillis);
1055                 }
1056             }
1057 
1058             if (readOps != null) {
1059                 if (outOps == null) {
1060                     outOps = new LinkedList<>();
1061                 }
1062                 // Add the read ops to output
1063                 final int opCount = readOps.size();
1064                 for (int i = opCount - 1; i >= 0; i--) {
1065                     outOps.offerFirst(readOps.get(i));
1066                 }
1067             }
1068 
1069             return outOps;
1070         }
1071 
handlePersistHistoricalOpsRecursiveDLocked(@onNull File newBaseDir, @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps, @NonNull Set<String> oldFileNames, int depth)1072         private void handlePersistHistoricalOpsRecursiveDLocked(@NonNull File newBaseDir,
1073                 @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps,
1074                 @NonNull Set<String> oldFileNames, int depth)
1075                 throws IOException, XmlPullParserException {
1076             final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
1077                     depth) * mBaseSnapshotInterval;
1078             final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
1079                     depth + 1) * mBaseSnapshotInterval;
1080 
1081             if (passedOps == null || passedOps.isEmpty()) {
1082                 if (!oldFileNames.isEmpty()) {
1083                     // If there is an old file we need to copy it over to the new state.
1084                     final File oldFile = generateFile(oldBaseDir, depth);
1085                     if (oldFileNames.remove(oldFile.getName())) {
1086                         final File newFile = generateFile(newBaseDir, depth);
1087                         Files.createLink(newFile.toPath(), oldFile.toPath());
1088                     }
1089                     handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
1090                             passedOps, oldFileNames, depth + 1);
1091                 }
1092                 return;
1093             }
1094 
1095             if (DEBUG) {
1096                 enforceOpsWellFormed(passedOps);
1097             }
1098 
1099             // Normalize passed ops time to be based off this interval start
1100             final int passedOpCount = passedOps.size();
1101             for (int i = 0; i < passedOpCount; i++) {
1102                 final HistoricalOps passedOp = passedOps.get(i);
1103                 passedOp.offsetBeginAndEndTime(-previousIntervalEndMillis);
1104             }
1105 
1106             if (DEBUG) {
1107                 enforceOpsWellFormed(passedOps);
1108             }
1109 
1110             // Read persisted ops for this interval
1111             final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir,
1112                     previousIntervalEndMillis, currentIntervalEndMillis,
1113                     Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/,
1114                     null /*filterAttributionTag*/, null /*filterOpNames*/, 0 /*filter*/,
1115                     Long.MIN_VALUE /*filterBeginTimeMillis*/,
1116                     Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL, null, depth,
1117                     null /*historyFiles*/);
1118             if (DEBUG) {
1119                 enforceOpsWellFormed(existingOps);
1120             }
1121 
1122             // Offset existing ops to account for elapsed time
1123             if (existingOps != null) {
1124                 final int existingOpCount = existingOps.size();
1125                 if (existingOpCount > 0) {
1126                     // Compute elapsed time
1127                     final long elapsedTimeMillis = passedOps.get(passedOps.size() - 1)
1128                             .getEndTimeMillis();
1129                     for (int i = 0; i < existingOpCount; i++) {
1130                         final HistoricalOps existingOp = existingOps.get(i);
1131                         existingOp.offsetBeginAndEndTime(elapsedTimeMillis);
1132                     }
1133                 }
1134             }
1135 
1136             if (DEBUG) {
1137                 enforceOpsWellFormed(existingOps);
1138             }
1139 
1140             final long slotDurationMillis = previousIntervalEndMillis;
1141 
1142             // Consolidate passed ops at the current slot duration ensuring each snapshot is
1143             // full. To achieve this we put all passed and existing ops in a list and will
1144             // merge them to ensure each represents a snapshot at the current granularity.
1145             final List<HistoricalOps> allOps = new LinkedList<>(passedOps);
1146             if (existingOps != null) {
1147                 allOps.addAll(existingOps);
1148             }
1149 
1150             if (DEBUG) {
1151                 enforceOpsWellFormed(allOps);
1152             }
1153 
1154             // Compute ops to persist and overflow ops
1155             List<HistoricalOps> persistedOps = null;
1156             List<HistoricalOps> overflowedOps = null;
1157 
1158             // We move a snapshot into the next level only if the start time is
1159             // after the end of the current interval. This avoids rewriting all
1160             // files to propagate the information added to the history on every
1161             // iteration. Instead, we would rewrite the next level file only if
1162             // an entire snapshot from the previous level is being propagated.
1163             // The trade off is that we need to store how much the last snapshot
1164             // of the current interval overflows past the interval end. We write
1165             // the overflow data to avoid parsing all snapshots on query.
1166             long intervalOverflowMillis = 0;
1167             final int opCount = allOps.size();
1168             for (int i = 0; i < opCount; i++) {
1169                 final HistoricalOps op = allOps.get(i);
1170                 final HistoricalOps persistedOp;
1171                 final HistoricalOps overflowedOp;
1172                 if (op.getEndTimeMillis() <= currentIntervalEndMillis) {
1173                     persistedOp = op;
1174                     overflowedOp = null;
1175                 } else if (op.getBeginTimeMillis() < currentIntervalEndMillis) {
1176                     persistedOp = op;
1177                     intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
1178                     if (intervalOverflowMillis > previousIntervalEndMillis) {
1179                         final double splitScale = (double) intervalOverflowMillis
1180                                 / op.getDurationMillis();
1181                         overflowedOp = spliceFromEnd(op, splitScale);
1182                         intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
1183                     } else {
1184                         overflowedOp = null;
1185                     }
1186                 } else {
1187                     persistedOp = null;
1188                     overflowedOp = op;
1189                 }
1190                 if (persistedOp != null) {
1191                     if (persistedOps == null) {
1192                         persistedOps = new ArrayList<>();
1193                     }
1194                     persistedOps.add(persistedOp);
1195                 }
1196                 if (overflowedOp != null) {
1197                     if (overflowedOps == null) {
1198                         overflowedOps = new ArrayList<>();
1199                     }
1200                     overflowedOps.add(overflowedOp);
1201                 }
1202             }
1203 
1204             if (DEBUG) {
1205                 enforceOpsWellFormed(persistedOps);
1206                 enforceOpsWellFormed(overflowedOps);
1207             }
1208 
1209             final File newFile = generateFile(newBaseDir, depth);
1210             oldFileNames.remove(newFile.getName());
1211 
1212             if (persistedOps != null) {
1213                 normalizeSnapshotForSlotDuration(persistedOps, slotDurationMillis);
1214                 writeHistoricalOpsDLocked(persistedOps, intervalOverflowMillis, newFile);
1215                 if (DEBUG) {
1216                     Slog.i(LOG_TAG, "Persisted at depth: " + depth + " file: " + newFile
1217                             + " ops:\n" + opsToDebugString(persistedOps));
1218                     enforceOpsWellFormed(persistedOps);
1219                 }
1220             }
1221 
1222             handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
1223                     overflowedOps, oldFileNames, depth + 1);
1224         }
1225 
readHistoricalOpsLocked(File baseDir, long intervalBeginMillis, long intervalEndMillis, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @Nullable long[] cumulativeOverflowMillis, int depth, @NonNull Set<String> historyFiles)1226         private @Nullable List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
1227                 long intervalBeginMillis, long intervalEndMillis, int filterUid,
1228                 @Nullable String filterPackageName, @Nullable String filterAttributionTag,
1229                 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
1230                 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags,
1231                 @Nullable long[] cumulativeOverflowMillis, int depth,
1232                 @NonNull Set<String> historyFiles)
1233                 throws IOException, XmlPullParserException {
1234             final File file = generateFile(baseDir, depth);
1235             if (historyFiles != null) {
1236                 historyFiles.remove(file.getName());
1237             }
1238             if (filterBeginTimeMillis >= filterEndTimeMillis
1239                     || filterEndTimeMillis < intervalBeginMillis) {
1240                 // Don't go deeper
1241                 return Collections.emptyList();
1242             }
1243             if (filterBeginTimeMillis >= (intervalEndMillis
1244                     + ((intervalEndMillis - intervalBeginMillis) / mIntervalCompressionMultiplier)
1245                     + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0))
1246                     || !file.exists()) {
1247                 if (historyFiles == null || historyFiles.isEmpty()) {
1248                     // Don't go deeper
1249                     return Collections.emptyList();
1250                 } else {
1251                     // Keep diving
1252                     return null;
1253                 }
1254             }
1255             return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterAttributionTag,
1256                     filterOpNames, filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
1257                     cumulativeOverflowMillis);
1258         }
1259 
readHistoricalOpsLocked(@onNull File file, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @Nullable long[] cumulativeOverflowMillis)1260         private @Nullable  List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file,
1261                 int filterUid, @Nullable String filterPackageName,
1262                 @Nullable String filterAttributionTag, @Nullable String[] filterOpNames,
1263                 @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
1264                 long filterEndTimeMillis, @OpFlags int filterFlags,
1265                 @Nullable long[] cumulativeOverflowMillis)
1266                 throws IOException, XmlPullParserException {
1267             if (DEBUG) {
1268                 Slog.i(LOG_TAG, "Reading ops from:" + file);
1269             }
1270             List<HistoricalOps> allOps = null;
1271             try (FileInputStream stream = new FileInputStream(file)) {
1272                 final TypedXmlPullParser parser = Xml.resolvePullParser(stream);
1273                 XmlUtils.beginDocument(parser, TAG_HISTORY);
1274 
1275                 // We haven't released version 1 and have more detailed
1276                 // accounting - just nuke the current state
1277                 final int version = parser.getAttributeInt(null, ATTR_VERSION);
1278                 if (CURRENT_VERSION == 2 && version < CURRENT_VERSION) {
1279                     throw new IllegalStateException("Dropping unsupported history "
1280                             + "version 1 for file:" + file);
1281                 }
1282 
1283                 final long overflowMillis = parser.getAttributeLong(null, ATTR_OVERFLOW, 0);
1284                 final int depth = parser.getDepth();
1285                 while (XmlUtils.nextElementWithin(parser, depth)) {
1286                     if (TAG_OPS.equals(parser.getName())) {
1287                         final HistoricalOps ops = readeHistoricalOpsDLocked(parser, filterUid,
1288                                 filterPackageName, filterAttributionTag, filterOpNames, filter,
1289                                 filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
1290                                 cumulativeOverflowMillis);
1291                         if (ops == null) {
1292                             continue;
1293                         }
1294                         if (ops.isEmpty()) {
1295                             XmlUtils.skipCurrentTag(parser);
1296                             continue;
1297                         }
1298                         if (allOps == null) {
1299                             allOps = new ArrayList<>();
1300                         }
1301                         allOps.add(ops);
1302                     }
1303                 }
1304                 if (cumulativeOverflowMillis != null) {
1305                     cumulativeOverflowMillis[0] += overflowMillis;
1306                 }
1307             } catch (FileNotFoundException e) {
1308                 Slog.i(LOG_TAG, "No history file: " + file.getName());
1309                 return Collections.emptyList();
1310             }
1311             if (DEBUG) {
1312                 if (allOps != null) {
1313                     Slog.i(LOG_TAG, "Read from file: " + file + " ops:\n"
1314                             + opsToDebugString(allOps));
1315                     enforceOpsWellFormed(allOps);
1316                 }
1317             }
1318             return allOps;
1319         }
1320 
readeHistoricalOpsDLocked( @onNull TypedXmlPullParser parser, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @Nullable long[] cumulativeOverflowMillis)1321         private @Nullable HistoricalOps readeHistoricalOpsDLocked(
1322                 @NonNull TypedXmlPullParser parser, int filterUid,
1323                 @Nullable String filterPackageName,
1324                 @Nullable String filterAttributionTag, @Nullable String[] filterOpNames,
1325                 @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
1326                 long filterEndTimeMillis, @OpFlags int filterFlags,
1327                 @Nullable long[] cumulativeOverflowMillis)
1328                 throws IOException, XmlPullParserException {
1329             final long beginTimeMillis = parser.getAttributeLong(null, ATTR_BEGIN_TIME, 0)
1330                     + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
1331             final long endTimeMillis = parser.getAttributeLong(null, ATTR_END_TIME, 0)
1332                     + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
1333             // Keep reading as subsequent records may start matching
1334             if (filterEndTimeMillis < beginTimeMillis) {
1335                 return null;
1336             }
1337             // Stop reading as subsequent records will not match
1338             if (filterBeginTimeMillis > endTimeMillis) {
1339                 return new HistoricalOps(0, 0);
1340             }
1341             final long filteredBeginTimeMillis = Math.max(beginTimeMillis, filterBeginTimeMillis);
1342             final long filteredEndTimeMillis = Math.min(endTimeMillis, filterEndTimeMillis);
1343             // // Keep reading as subsequent records may start matching
1344             // if (filteredEndTimeMillis - filterBeginTimeMillis <= 0) {
1345             //     return null;
1346             // }
1347             final double filterScale = (double) (filteredEndTimeMillis - filteredBeginTimeMillis)
1348                     / (double) (endTimeMillis - beginTimeMillis);
1349             HistoricalOps ops = null;
1350             final int depth = parser.getDepth();
1351             while (XmlUtils.nextElementWithin(parser, depth)) {
1352                 if (TAG_UID.equals(parser.getName())) {
1353                     final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser,
1354                             filterUid, filterPackageName, filterAttributionTag, filterOpNames,
1355                             filter, filterFlags, filterScale);
1356                     if (ops == null) {
1357                         ops = returnedOps;
1358                     }
1359                 }
1360             }
1361             if (ops != null) {
1362                 ops.setBeginAndEndTime(filteredBeginTimeMillis, filteredEndTimeMillis);
1363             }
1364             return ops;
1365         }
1366 
readHistoricalUidOpsDLocked( @ullable HistoricalOps ops, @NonNull TypedXmlPullParser parser, int filterUid, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, double filterScale)1367         private @Nullable HistoricalOps readHistoricalUidOpsDLocked(
1368                 @Nullable HistoricalOps ops, @NonNull TypedXmlPullParser parser, int filterUid,
1369                 @Nullable String filterPackageName, @Nullable String filterAttributionTag,
1370                 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
1371                 @OpFlags int filterFlags, double filterScale)
1372                 throws IOException, XmlPullParserException {
1373             final int uid = parser.getAttributeInt(null, ATTR_NAME);
1374             if ((filter & FILTER_BY_UID) != 0 && filterUid != uid) {
1375                 XmlUtils.skipCurrentTag(parser);
1376                 return null;
1377             }
1378             final int depth = parser.getDepth();
1379             while (XmlUtils.nextElementWithin(parser, depth)) {
1380                 if (TAG_PACKAGE.equals(parser.getName())) {
1381                     final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops, uid,
1382                             parser, filterPackageName, filterAttributionTag, filterOpNames, filter,
1383                             filterFlags, filterScale);
1384                     if (ops == null) {
1385                         ops = returnedOps;
1386                     }
1387                 }
1388             }
1389             return ops;
1390         }
1391 
readHistoricalPackageOpsDLocked( @ullable HistoricalOps ops, int uid, @NonNull TypedXmlPullParser parser, @Nullable String filterPackageName, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, double filterScale)1392         private @Nullable HistoricalOps readHistoricalPackageOpsDLocked(
1393                 @Nullable HistoricalOps ops, int uid, @NonNull TypedXmlPullParser parser,
1394                 @Nullable String filterPackageName, @Nullable String filterAttributionTag,
1395                 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
1396                 @OpFlags int filterFlags, double filterScale)
1397                 throws IOException, XmlPullParserException {
1398             final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME);
1399             if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !filterPackageName.equals(packageName)) {
1400                 XmlUtils.skipCurrentTag(parser);
1401                 return null;
1402             }
1403             final int depth = parser.getDepth();
1404             while (XmlUtils.nextElementWithin(parser, depth)) {
1405                 if (TAG_ATTRIBUTION.equals(parser.getName())) {
1406                     final HistoricalOps returnedOps = readHistoricalAttributionOpsDLocked(ops, uid,
1407                             packageName, parser, filterAttributionTag, filterOpNames, filter,
1408                             filterFlags, filterScale);
1409                     if (ops == null) {
1410                         ops = returnedOps;
1411                     }
1412                 }
1413             }
1414             return ops;
1415         }
1416 
readHistoricalAttributionOpsDLocked( @ullable HistoricalOps ops, int uid, String packageName, @NonNull TypedXmlPullParser parser, @Nullable String filterAttributionTag, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, double filterScale)1417         private @Nullable HistoricalOps readHistoricalAttributionOpsDLocked(
1418                 @Nullable HistoricalOps ops, int uid, String packageName,
1419                 @NonNull TypedXmlPullParser parser, @Nullable String filterAttributionTag,
1420                 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
1421                 @OpFlags int filterFlags, double filterScale)
1422                 throws IOException, XmlPullParserException {
1423             final String attributionTag = XmlUtils.readStringAttribute(parser, ATTR_NAME);
1424             if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(filterAttributionTag,
1425                     attributionTag)) {
1426                 XmlUtils.skipCurrentTag(parser);
1427                 return null;
1428             }
1429             final int depth = parser.getDepth();
1430             while (XmlUtils.nextElementWithin(parser, depth)) {
1431                 if (TAG_OP.equals(parser.getName())) {
1432                     final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid, packageName,
1433                             attributionTag, parser, filterOpNames, filter, filterFlags,
1434                             filterScale);
1435                     if (ops == null) {
1436                         ops = returnedOps;
1437                     }
1438                 }
1439             }
1440             return ops;
1441         }
1442 
readHistoricalOpDLocked(@ullable HistoricalOps ops, int uid, @NonNull String packageName, @Nullable String attributionTag, @NonNull TypedXmlPullParser parser, @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, double filterScale)1443         private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops,
1444                 int uid, @NonNull String packageName, @Nullable String attributionTag,
1445                 @NonNull TypedXmlPullParser parser, @Nullable String[] filterOpNames,
1446                 @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags,
1447                 double filterScale)
1448                 throws IOException, XmlPullParserException {
1449             final int op = parser.getAttributeInt(null, ATTR_NAME);
1450             if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(filterOpNames,
1451                     AppOpsManager.opToPublicName(op))) {
1452                 XmlUtils.skipCurrentTag(parser);
1453                 return null;
1454             }
1455             final int depth = parser.getDepth();
1456             while (XmlUtils.nextElementWithin(parser, depth)) {
1457                 if (TAG_STATE.equals(parser.getName())) {
1458                     final HistoricalOps returnedOps = readStateDLocked(ops, uid,
1459                             packageName, attributionTag, op, parser, filterFlags, filterScale);
1460                     if (ops == null) {
1461                         ops = returnedOps;
1462                     }
1463                 }
1464             }
1465             return ops;
1466         }
1467 
readStateDLocked(@ullable HistoricalOps ops, int uid, @NonNull String packageName, @Nullable String attributionTag, int op, @NonNull TypedXmlPullParser parser, @OpFlags int filterFlags, double filterScale)1468         private @Nullable HistoricalOps readStateDLocked(@Nullable HistoricalOps ops,
1469                 int uid, @NonNull String packageName, @Nullable String attributionTag, int op,
1470                 @NonNull TypedXmlPullParser parser, @OpFlags int filterFlags, double filterScale)
1471                 throws IOException, XmlPullParserException {
1472             final long key = parser.getAttributeLong(null, ATTR_NAME);
1473             final int flags = AppOpsManager.extractFlagsFromKey(key) & filterFlags;
1474             if (flags == 0) {
1475                 return null;
1476             }
1477             final int uidState = AppOpsManager.extractUidStateFromKey(key);
1478             long accessCount = parser.getAttributeLong(null, ATTR_ACCESS_COUNT, 0);
1479             if (accessCount > 0) {
1480                 if (!Double.isNaN(filterScale)) {
1481                     accessCount = (long) HistoricalOps.round(
1482                             (double) accessCount * filterScale);
1483                 }
1484                 if (ops == null) {
1485                     ops = new HistoricalOps(0, 0);
1486                 }
1487                 ops.increaseAccessCount(op, uid, packageName, attributionTag, uidState, flags,
1488                         accessCount);
1489             }
1490             long rejectCount = parser.getAttributeLong(null, ATTR_REJECT_COUNT, 0);
1491             if (rejectCount > 0) {
1492                 if (!Double.isNaN(filterScale)) {
1493                     rejectCount = (long) HistoricalOps.round(
1494                             (double) rejectCount * filterScale);
1495                 }
1496                 if (ops == null) {
1497                     ops = new HistoricalOps(0, 0);
1498                 }
1499                 ops.increaseRejectCount(op, uid, packageName, attributionTag, uidState, flags,
1500                         rejectCount);
1501             }
1502             long accessDuration =  parser.getAttributeLong(null, ATTR_ACCESS_DURATION, 0);
1503             if (accessDuration > 0) {
1504                 if (!Double.isNaN(filterScale)) {
1505                     accessDuration = (long) HistoricalOps.round(
1506                             (double) accessDuration * filterScale);
1507                 }
1508                 if (ops == null) {
1509                     ops = new HistoricalOps(0, 0);
1510                 }
1511                 ops.increaseAccessDuration(op, uid, packageName, attributionTag, uidState, flags,
1512                         accessDuration);
1513             }
1514             return ops;
1515         }
1516 
writeHistoricalOpsDLocked(@ullable List<HistoricalOps> allOps, long intervalOverflowMillis, @NonNull File file)1517         private void writeHistoricalOpsDLocked(@Nullable List<HistoricalOps> allOps,
1518                 long intervalOverflowMillis, @NonNull File file) throws IOException {
1519             final FileOutputStream output = sHistoricalAppOpsDir.openWrite(file);
1520             try {
1521                 final TypedXmlSerializer serializer = Xml.resolveSerializer(output);
1522                 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
1523                         true);
1524                 serializer.startDocument(null, true);
1525                 serializer.startTag(null, TAG_HISTORY);
1526                 serializer.attributeInt(null, ATTR_VERSION, CURRENT_VERSION);
1527                 if (intervalOverflowMillis != 0) {
1528                     serializer.attributeLong(null, ATTR_OVERFLOW, intervalOverflowMillis);
1529                 }
1530                 if (allOps != null) {
1531                     final int opsCount = allOps.size();
1532                     for (int i = 0; i < opsCount; i++) {
1533                         final HistoricalOps ops = allOps.get(i);
1534                         writeHistoricalOpDLocked(ops, serializer);
1535                     }
1536                 }
1537                 serializer.endTag(null, TAG_HISTORY);
1538                 serializer.endDocument();
1539                 sHistoricalAppOpsDir.closeWrite(output);
1540             } catch (IOException e) {
1541                 sHistoricalAppOpsDir.failWrite(output);
1542                 throw e;
1543             }
1544         }
1545 
writeHistoricalOpDLocked(@onNull HistoricalOps ops, @NonNull TypedXmlSerializer serializer)1546         private void writeHistoricalOpDLocked(@NonNull HistoricalOps ops,
1547                 @NonNull TypedXmlSerializer serializer) throws IOException {
1548             serializer.startTag(null, TAG_OPS);
1549             serializer.attributeLong(null, ATTR_BEGIN_TIME, ops.getBeginTimeMillis());
1550             serializer.attributeLong(null, ATTR_END_TIME, ops.getEndTimeMillis());
1551             final int uidCount = ops.getUidCount();
1552             for (int i = 0; i < uidCount; i++) {
1553                 final HistoricalUidOps uidOp = ops.getUidOpsAt(i);
1554                 writeHistoricalUidOpsDLocked(uidOp, serializer);
1555             }
1556             serializer.endTag(null, TAG_OPS);
1557         }
1558 
writeHistoricalUidOpsDLocked(@onNull HistoricalUidOps uidOps, @NonNull TypedXmlSerializer serializer)1559         private void writeHistoricalUidOpsDLocked(@NonNull HistoricalUidOps uidOps,
1560                 @NonNull TypedXmlSerializer serializer) throws IOException {
1561             serializer.startTag(null, TAG_UID);
1562             serializer.attributeInt(null, ATTR_NAME, uidOps.getUid());
1563             final int packageCount = uidOps.getPackageCount();
1564             for (int i = 0; i < packageCount; i++) {
1565                 final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(i);
1566                 writeHistoricalPackageOpsDLocked(packageOps, serializer);
1567             }
1568             serializer.endTag(null, TAG_UID);
1569         }
1570 
writeHistoricalPackageOpsDLocked(@onNull HistoricalPackageOps packageOps, @NonNull TypedXmlSerializer serializer)1571         private void writeHistoricalPackageOpsDLocked(@NonNull HistoricalPackageOps packageOps,
1572                 @NonNull TypedXmlSerializer serializer) throws IOException {
1573             serializer.startTag(null, TAG_PACKAGE);
1574             serializer.attributeInterned(null, ATTR_NAME, packageOps.getPackageName());
1575             final int numAttributions = packageOps.getAttributedOpsCount();
1576             for (int i = 0; i < numAttributions; i++) {
1577                 final AppOpsManager.AttributedHistoricalOps op = packageOps.getAttributedOpsAt(i);
1578                 writeHistoricalAttributionOpsDLocked(op, serializer);
1579             }
1580             serializer.endTag(null, TAG_PACKAGE);
1581         }
1582 
writeHistoricalAttributionOpsDLocked( @onNull AppOpsManager.AttributedHistoricalOps attributionOps, @NonNull TypedXmlSerializer serializer)1583         private void writeHistoricalAttributionOpsDLocked(
1584                 @NonNull AppOpsManager.AttributedHistoricalOps attributionOps,
1585                 @NonNull TypedXmlSerializer serializer) throws IOException {
1586             serializer.startTag(null, TAG_ATTRIBUTION);
1587             XmlUtils.writeStringAttribute(serializer, ATTR_NAME, attributionOps.getTag());
1588             final int opCount = attributionOps.getOpCount();
1589             for (int i = 0; i < opCount; i++) {
1590                 final HistoricalOp op = attributionOps.getOpAt(i);
1591                 writeHistoricalOpDLocked(op, serializer);
1592             }
1593             serializer.endTag(null, TAG_ATTRIBUTION);
1594         }
1595 
writeHistoricalOpDLocked(@onNull HistoricalOp op, @NonNull TypedXmlSerializer serializer)1596         private void writeHistoricalOpDLocked(@NonNull HistoricalOp op,
1597                 @NonNull TypedXmlSerializer serializer) throws IOException {
1598             final LongSparseArray keys = op.collectKeys();
1599             if (keys == null || keys.size() <= 0) {
1600                 return;
1601             }
1602             serializer.startTag(null, TAG_OP);
1603             serializer.attributeInt(null, ATTR_NAME, op.getOpCode());
1604             final int keyCount = keys.size();
1605             for (int i = 0; i < keyCount; i++) {
1606                 writeStateOnLocked(op, keys.keyAt(i), serializer);
1607             }
1608             serializer.endTag(null, TAG_OP);
1609         }
1610 
writeStateOnLocked(@onNull HistoricalOp op, long key, @NonNull TypedXmlSerializer serializer)1611         private void writeStateOnLocked(@NonNull HistoricalOp op, long key,
1612                 @NonNull TypedXmlSerializer serializer) throws IOException {
1613             final int uidState = AppOpsManager.extractUidStateFromKey(key);
1614             final int flags = AppOpsManager.extractFlagsFromKey(key);
1615 
1616             final long accessCount = op.getAccessCount(uidState, uidState, flags);
1617             final long rejectCount = op.getRejectCount(uidState, uidState, flags);
1618             final long accessDuration = op.getAccessDuration(uidState, uidState, flags);
1619 
1620             if (accessCount <= 0 && rejectCount <= 0 && accessDuration <= 0) {
1621                 return;
1622             }
1623 
1624             serializer.startTag(null, TAG_STATE);
1625             serializer.attributeLong(null, ATTR_NAME, key);
1626             if (accessCount > 0) {
1627                 serializer.attributeLong(null, ATTR_ACCESS_COUNT, accessCount);
1628             }
1629             if (rejectCount > 0) {
1630                 serializer.attributeLong(null, ATTR_REJECT_COUNT, rejectCount);
1631             }
1632             if (accessDuration > 0) {
1633                 serializer.attributeLong(null, ATTR_ACCESS_DURATION, accessDuration);
1634             }
1635             serializer.endTag(null, TAG_STATE);
1636         }
1637 
enforceOpsWellFormed(@onNull List<HistoricalOps> ops)1638         private static void enforceOpsWellFormed(@NonNull List<HistoricalOps> ops) {
1639             if (ops == null) {
1640                 return;
1641             }
1642             HistoricalOps previous;
1643             HistoricalOps current = null;
1644             final int opsCount = ops.size();
1645             for (int i = 0; i < opsCount; i++) {
1646                 previous = current;
1647                 current = ops.get(i);
1648                 if (current.isEmpty()) {
1649                     throw new IllegalStateException("Empty ops:\n"
1650                             + opsToDebugString(ops));
1651                 }
1652                 if (current.getEndTimeMillis() < current.getBeginTimeMillis()) {
1653                     throw new IllegalStateException("Begin after end:\n"
1654                             + opsToDebugString(ops));
1655                 }
1656                 if (previous != null) {
1657                     if (previous.getEndTimeMillis() > current.getBeginTimeMillis()) {
1658                         throw new IllegalStateException("Intersecting ops:\n"
1659                                 + opsToDebugString(ops));
1660                     }
1661                     if (previous.getBeginTimeMillis() > current.getBeginTimeMillis()) {
1662                         throw new IllegalStateException("Non increasing ops:\n"
1663                                 + opsToDebugString(ops));
1664                     }
1665                 }
1666             }
1667         }
1668 
computeGlobalIntervalBeginMillis(int depth)1669         private long computeGlobalIntervalBeginMillis(int depth) {
1670             long beginTimeMillis = 0;
1671             for (int i = 0; i < depth + 1; i++) {
1672                 beginTimeMillis += Math.pow(mIntervalCompressionMultiplier, i);
1673             }
1674             return beginTimeMillis * mBaseSnapshotInterval;
1675         }
1676 
spliceFromEnd(@onNull HistoricalOps ops, double spliceRatio)1677         private static @NonNull HistoricalOps spliceFromEnd(@NonNull HistoricalOps ops,
1678                 double spliceRatio) {
1679             if (DEBUG) {
1680                 Slog.w(LOG_TAG, "Splicing from end:" + ops + " ratio:" + spliceRatio);
1681             }
1682             final HistoricalOps splice = ops.spliceFromEnd(spliceRatio);
1683             if (DEBUG) {
1684                 Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
1685             }
1686             return splice;
1687         }
1688 
1689 
spliceFromBeginning(@onNull HistoricalOps ops, double spliceRatio)1690         private static @NonNull HistoricalOps spliceFromBeginning(@NonNull HistoricalOps ops,
1691                 double spliceRatio) {
1692             if (DEBUG) {
1693                 Slog.w(LOG_TAG, "Splicing from beginning:" + ops + " ratio:" + spliceRatio);
1694             }
1695             final HistoricalOps splice = ops.spliceFromBeginning(spliceRatio);
1696             if (DEBUG) {
1697                 Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
1698             }
1699             return splice;
1700         }
1701 
normalizeSnapshotForSlotDuration(@onNull List<HistoricalOps> ops, long slotDurationMillis)1702         private static void normalizeSnapshotForSlotDuration(@NonNull List<HistoricalOps> ops,
1703                 long slotDurationMillis) {
1704             if (DEBUG) {
1705                 Slog.i(LOG_TAG, "Normalizing for slot duration: " + slotDurationMillis
1706                         + " ops:\n" + opsToDebugString(ops));
1707                 enforceOpsWellFormed(ops);
1708             }
1709             long slotBeginTimeMillis;
1710             final int opCount = ops.size();
1711             for (int processedIdx = opCount - 1; processedIdx >= 0; processedIdx--) {
1712                 final HistoricalOps processedOp = ops.get(processedIdx);
1713                 slotBeginTimeMillis = Math.max(processedOp.getEndTimeMillis()
1714                         - slotDurationMillis, 0);
1715                 for (int candidateIdx = processedIdx - 1; candidateIdx >= 0; candidateIdx--) {
1716                     final HistoricalOps candidateOp = ops.get(candidateIdx);
1717                     final long candidateSlotIntersectionMillis = candidateOp.getEndTimeMillis()
1718                             - Math.min(slotBeginTimeMillis, processedOp.getBeginTimeMillis());
1719                     if (candidateSlotIntersectionMillis <= 0) {
1720                         break;
1721                     }
1722                     final float candidateSplitRatio = candidateSlotIntersectionMillis
1723                             / (float) candidateOp.getDurationMillis();
1724                     if (Float.compare(candidateSplitRatio, 1.0f) >= 0) {
1725                         ops.remove(candidateIdx);
1726                         processedIdx--;
1727                         processedOp.merge(candidateOp);
1728                     } else {
1729                         final HistoricalOps endSplice = spliceFromEnd(candidateOp,
1730                                 candidateSplitRatio);
1731                         if (endSplice != null) {
1732                             processedOp.merge(endSplice);
1733                         }
1734                         if (candidateOp.isEmpty()) {
1735                             ops.remove(candidateIdx);
1736                             processedIdx--;
1737                         }
1738                     }
1739                 }
1740             }
1741             if (DEBUG) {
1742                 Slog.i(LOG_TAG, "Normalized for slot duration: " + slotDurationMillis
1743                         + " ops:\n" + opsToDebugString(ops));
1744                 enforceOpsWellFormed(ops);
1745             }
1746         }
1747 
opsToDebugString(@onNull List<HistoricalOps> ops)1748         private static @NonNull String opsToDebugString(@NonNull List<HistoricalOps> ops) {
1749             StringBuilder builder = new StringBuilder();
1750             final int opCount = ops.size();
1751             for (int i = 0; i < opCount; i++) {
1752                 builder.append("  ");
1753                 builder.append(ops.get(i));
1754                 if (i < opCount - 1) {
1755                     builder.append('\n');
1756                 }
1757             }
1758             return builder.toString();
1759         }
1760 
getHistoricalFileNames(@onNull File historyDir)1761         private static Set<String> getHistoricalFileNames(@NonNull File historyDir)  {
1762             final File[] files = historyDir.listFiles();
1763             if (files == null) {
1764                 return Collections.emptySet();
1765             }
1766             final ArraySet<String> fileNames = new ArraySet<>(files.length);
1767             for (File file : files) {
1768                 fileNames.add(file.getName());
1769 
1770             }
1771             return fileNames;
1772         }
1773     }
1774 
1775     private static class HistoricalFilesInvariant {
1776         private final @NonNull List<File> mBeginFiles = new ArrayList<>();
1777 
startTracking(@onNull File folder)1778         public void startTracking(@NonNull File folder) {
1779             final File[] files = folder.listFiles();
1780             if (files != null) {
1781                 Collections.addAll(mBeginFiles, files);
1782             }
1783         }
1784 
stopTracking(@onNull File folder)1785         public void stopTracking(@NonNull File folder) {
1786             final List<File> endFiles = new ArrayList<>();
1787             final File[] files = folder.listFiles();
1788             if (files != null) {
1789                 Collections.addAll(endFiles, files);
1790             }
1791             final long beginOldestFileOffsetMillis = getOldestFileOffsetMillis(mBeginFiles);
1792             final long endOldestFileOffsetMillis = getOldestFileOffsetMillis(endFiles);
1793             if (endOldestFileOffsetMillis < beginOldestFileOffsetMillis) {
1794                 final String message = "History loss detected!"
1795                         + "\nold files: " + mBeginFiles;
1796                 wtf(message, null, folder);
1797                 throw new IllegalStateException(message);
1798             }
1799         }
1800 
getOldestFileOffsetMillis(@onNull List<File> files)1801         private static long getOldestFileOffsetMillis(@NonNull List<File> files) {
1802             if (files.isEmpty()) {
1803                 return 0;
1804             }
1805             String longestName = files.get(0).getName();
1806             final int fileCount = files.size();
1807             for (int i = 1; i < fileCount; i++) {
1808                 final File file = files.get(i);
1809                 if (file.getName().length() > longestName.length()) {
1810                     longestName = file.getName();
1811                 }
1812             }
1813             return Long.parseLong(longestName.replace(HISTORY_FILE_SUFFIX, ""));
1814         }
1815     }
1816 
1817     private final class StringDumpVisitor implements AppOpsManager.HistoricalOpsVisitor {
1818         private final long mNow = System.currentTimeMillis();
1819 
1820         private final SimpleDateFormat mDateFormatter = new SimpleDateFormat(
1821                 "yyyy-MM-dd HH:mm:ss.SSS");
1822         private final Date mDate = new Date();
1823 
1824         private final @NonNull String mOpsPrefix;
1825         private final @NonNull String mUidPrefix;
1826         private final @NonNull String mPackagePrefix;
1827         private final @NonNull String mAttributionPrefix;
1828         private final @NonNull String mEntryPrefix;
1829         private final @NonNull String mUidStatePrefix;
1830         private final @NonNull PrintWriter mWriter;
1831         private final int mFilterUid;
1832         private final String mFilterPackage;
1833         private final String mFilterAttributionTag;
1834         private final int mFilterOp;
1835         private final @HistoricalOpsRequestFilter int mFilter;
1836 
StringDumpVisitor(@onNull String prefix, @NonNull PrintWriter writer, int filterUid, @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp, @HistoricalOpsRequestFilter int filter)1837         StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer, int filterUid,
1838                 @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp,
1839                 @HistoricalOpsRequestFilter int filter) {
1840             mOpsPrefix = prefix + "  ";
1841             mUidPrefix = mOpsPrefix + "  ";
1842             mPackagePrefix = mUidPrefix + "  ";
1843             mAttributionPrefix = mPackagePrefix + "  ";
1844             mEntryPrefix = mAttributionPrefix + "  ";
1845             mUidStatePrefix = mEntryPrefix + "  ";
1846             mWriter = writer;
1847             mFilterUid = filterUid;
1848             mFilterPackage = filterPackage;
1849             mFilterAttributionTag = filterAttributionTag;
1850             mFilterOp = filterOp;
1851             mFilter = filter;
1852         }
1853 
1854         @Override
visitHistoricalOps(HistoricalOps ops)1855         public void visitHistoricalOps(HistoricalOps ops) {
1856             mWriter.println();
1857             mWriter.print(mOpsPrefix);
1858             mWriter.println("snapshot:");
1859             mWriter.print(mUidPrefix);
1860             mWriter.print("begin = ");
1861             mDate.setTime(ops.getBeginTimeMillis());
1862             mWriter.print(mDateFormatter.format(mDate));
1863             mWriter.print("  (");
1864             TimeUtils.formatDuration(ops.getBeginTimeMillis() - mNow, mWriter);
1865             mWriter.println(")");
1866             mWriter.print(mUidPrefix);
1867             mWriter.print("end = ");
1868             mDate.setTime(ops.getEndTimeMillis());
1869             mWriter.print(mDateFormatter.format(mDate));
1870             mWriter.print("  (");
1871             TimeUtils.formatDuration(ops.getEndTimeMillis() - mNow, mWriter);
1872             mWriter.println(")");
1873         }
1874 
1875         @Override
visitHistoricalUidOps(HistoricalUidOps ops)1876         public void visitHistoricalUidOps(HistoricalUidOps ops) {
1877             if ((mFilter & FILTER_BY_UID) != 0 && mFilterUid != ops.getUid()) {
1878                 return;
1879             }
1880             mWriter.println();
1881             mWriter.print(mUidPrefix);
1882             mWriter.print("Uid ");
1883             UserHandle.formatUid(mWriter, ops.getUid());
1884             mWriter.println(":");
1885         }
1886 
1887         @Override
visitHistoricalPackageOps(HistoricalPackageOps ops)1888         public void visitHistoricalPackageOps(HistoricalPackageOps ops) {
1889             if ((mFilter & FILTER_BY_PACKAGE_NAME) != 0 && !mFilterPackage.equals(
1890                     ops.getPackageName())) {
1891                 return;
1892             }
1893             mWriter.print(mPackagePrefix);
1894             mWriter.print("Package ");
1895             mWriter.print(ops.getPackageName());
1896             mWriter.println(":");
1897         }
1898 
1899         @Override
visitHistoricalAttributionOps(AppOpsManager.AttributedHistoricalOps ops)1900         public void visitHistoricalAttributionOps(AppOpsManager.AttributedHistoricalOps ops) {
1901             if ((mFilter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(mFilterPackage,
1902                     ops.getTag())) {
1903                 return;
1904             }
1905             mWriter.print(mAttributionPrefix);
1906             mWriter.print("Attribution ");
1907             mWriter.print(ops.getTag());
1908             mWriter.println(":");
1909         }
1910 
1911         @Override
visitHistoricalOp(HistoricalOp ops)1912         public void visitHistoricalOp(HistoricalOp ops) {
1913             if ((mFilter & FILTER_BY_OP_NAMES) != 0  && mFilterOp != ops.getOpCode()) {
1914                 return;
1915             }
1916             mWriter.print(mEntryPrefix);
1917             mWriter.print(AppOpsManager.opToName(ops.getOpCode()));
1918             mWriter.println(":");
1919             final LongSparseArray keys = ops.collectKeys();
1920             final int keyCount = keys.size();
1921             for (int i = 0; i < keyCount; i++) {
1922                 final long key = keys.keyAt(i);
1923                 final int uidState = AppOpsManager.extractUidStateFromKey(key);
1924                 final int flags = AppOpsManager.extractFlagsFromKey(key);
1925                 boolean printedUidState = false;
1926                 final long accessCount = ops.getAccessCount(uidState, uidState, flags);
1927                 if (accessCount > 0) {
1928                     if (!printedUidState) {
1929                         mWriter.print(mUidStatePrefix);
1930                         mWriter.print(AppOpsManager.keyToString(key));
1931                         mWriter.print(" = ");
1932                         printedUidState = true;
1933                     }
1934                     mWriter.print("access=");
1935                     mWriter.print(accessCount);
1936                 }
1937                 final long rejectCount = ops.getRejectCount(uidState, uidState, flags);
1938                 if (rejectCount > 0) {
1939                     if (!printedUidState) {
1940                         mWriter.print(mUidStatePrefix);
1941                         mWriter.print(AppOpsManager.keyToString(key));
1942                         mWriter.print(" = ");
1943                         printedUidState = true;
1944                     } else {
1945                         mWriter.print(", ");
1946                     }
1947                     mWriter.print("reject=");
1948                     mWriter.print(rejectCount);
1949                 }
1950                 final long accessDuration = ops.getAccessDuration(uidState, uidState, flags);
1951                 if (accessDuration > 0) {
1952                     if (!printedUidState) {
1953                         mWriter.print(mUidStatePrefix);
1954                         mWriter.print(AppOpsManager.keyToString(key));
1955                         mWriter.print(" = ");
1956                         printedUidState = true;
1957                     } else {
1958                         mWriter.print(", ");
1959                     }
1960                     mWriter.print("duration=");
1961                     TimeUtils.formatDuration(accessDuration, mWriter);
1962                 }
1963                 if (printedUidState) {
1964                     mWriter.println("");
1965                 }
1966             }
1967         }
1968     }
1969 
wtf(@ullable String message, @Nullable Throwable t, @Nullable File storage)1970     private static void wtf(@Nullable String message, @Nullable Throwable t,
1971             @Nullable File storage) {
1972         Slog.wtf(LOG_TAG, message, t);
1973         if (KEEP_WTF_LOG) {
1974             try {
1975                 final File file = new File(new File(Environment.getDataSystemDirectory(), "appops"),
1976                         "wtf" + TimeUtils.formatForLogging(System.currentTimeMillis()));
1977                 if (file.createNewFile()) {
1978                     try (PrintWriter writer = new PrintWriter(file)) {
1979                         if (t != null) {
1980                             writer.append('\n').append(t.toString());
1981                         }
1982                         writer.append('\n').append(Debug.getCallers(10));
1983                         if (storage != null) {
1984                             writer.append("\nfiles: " + Arrays.toString(storage.listFiles()));
1985                         } else {
1986                             writer.append("\nfiles: none");
1987                         }
1988                     }
1989                 }
1990             } catch (IOException e) {
1991                 /* ignore */
1992             }
1993         }
1994     }
1995 }
1996