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