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