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