blob: d723c7b5826d28e6fb9019b7e55ca94afd5745dc [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;
Svet Ganovaf189e32019-02-15 18:45:29 -080026import android.app.AppOpsManager.OpFlags;
Svet Ganov8455ba22019-01-02 13:05:56 -080027import android.app.AppOpsManager.UidState;
28import android.content.ContentResolver;
29import android.database.ContentObserver;
30import android.net.Uri;
Svet Ganov80f500c2019-01-19 17:22:45 -080031import android.os.Build;
Svet Ganov8455ba22019-01-02 13:05:56 -080032import android.os.Bundle;
Svet Ganov80f500c2019-01-19 17:22:45 -080033import android.os.Debug;
Svet Ganov8455ba22019-01-02 13:05:56 -080034import android.os.Environment;
35import android.os.Message;
36import android.os.Process;
37import android.os.RemoteCallback;
38import android.os.UserHandle;
39import android.provider.Settings;
40import android.util.ArraySet;
Svet Ganovaf189e32019-02-15 18:45:29 -080041import android.util.LongSparseArray;
Svet Ganov8455ba22019-01-02 13:05:56 -080042import android.util.Slog;
43import android.util.TimeUtils;
44import android.util.Xml;
Svet Ganov80f500c2019-01-19 17:22:45 -080045
Svet Ganov8455ba22019-01-02 13:05:56 -080046import com.android.internal.annotations.GuardedBy;
47import com.android.internal.os.AtomicDirectory;
48import com.android.internal.os.BackgroundThread;
49import com.android.internal.util.ArrayUtils;
50import com.android.internal.util.XmlUtils;
51import com.android.internal.util.function.pooled.PooledLambda;
52import com.android.server.FgThread;
Svet Ganov80f500c2019-01-19 17:22:45 -080053
Svet Ganov8455ba22019-01-02 13:05:56 -080054import org.xmlpull.v1.XmlPullParser;
55import org.xmlpull.v1.XmlPullParserException;
56import org.xmlpull.v1.XmlSerializer;
57
58import java.io.File;
59import java.io.FileInputStream;
60import java.io.FileNotFoundException;
61import java.io.FileOutputStream;
62import java.io.IOException;
63import java.io.PrintWriter;
64import java.nio.charset.StandardCharsets;
65import java.nio.file.Files;
66import java.text.SimpleDateFormat;
67import java.util.ArrayList;
Svet Ganov80f500c2019-01-19 17:22:45 -080068import java.util.Arrays;
Svet Ganov8455ba22019-01-02 13:05:56 -080069import java.util.Collections;
70import java.util.Date;
71import java.util.LinkedList;
72import java.util.List;
Svet Ganov80f500c2019-01-19 17:22:45 -080073import java.util.Set;
Svet Ganov8455ba22019-01-02 13:05:56 -080074import 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 */
112// TODO (bug:122218838): Make sure we handle start of epoch time
113// TODO (bug:122218838): Validate changed time is handled correctly
114final class HistoricalRegistry {
115 private static final boolean DEBUG = false;
Svet Ganov80f500c2019-01-19 17:22:45 -0800116 private static final boolean KEEP_WTF_LOG = Build.IS_DEBUGGABLE;
Svet Ganov8455ba22019-01-02 13:05:56 -0800117
118 private static final String LOG_TAG = HistoricalRegistry.class.getSimpleName();
119
120 private static final String PARAMETER_DELIMITER = ",";
121 private static final String PARAMETER_ASSIGNMENT = "=";
122
123 @GuardedBy("mLock")
124 private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
125
126 // Lock for read/write access to on disk state
127 private final Object mOnDiskLock = new Object();
128
129 //Lock for read/write access to in memory state
130 private final @NonNull Object mInMemoryLock;
131
132 private static final int MSG_WRITE_PENDING_HISTORY = 1;
133
134 // See mMode
Svet Ganov80f500c2019-01-19 17:22:45 -0800135 private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
Svet Ganov8455ba22019-01-02 13:05:56 -0800136
137 // See mBaseSnapshotInterval
138 private static final long DEFAULT_SNAPSHOT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15);
139
140 // See mIntervalCompressionMultiplier
141 private static final long DEFAULT_COMPRESSION_STEP = 10;
142
Svet Ganov80f500c2019-01-19 17:22:45 -0800143 private static final String HISTORY_FILE_SUFFIX = ".xml";
144
Svet Ganov8455ba22019-01-02 13:05:56 -0800145 /**
146 * Whether history is enabled.
147 */
148 @GuardedBy("mInMemoryLock")
Svet Ganovda077fb2019-03-04 22:58:26 -0800149 private int mMode = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
Svet Ganov8455ba22019-01-02 13:05:56 -0800150
151 /**
152 * This granularity has been chosen to allow clean delineation for intervals
153 * humans understand, 15 min, 60, min, a day, a week, a month (30 days).
154 */
155 @GuardedBy("mInMemoryLock")
156 private long mBaseSnapshotInterval = DEFAULT_SNAPSHOT_INTERVAL_MILLIS;
157
158 /**
159 * The compression between steps. Each subsequent step is this much longer
160 * in terms of duration and each snapshot is this much more apart from the
161 * previous step.
162 */
163 @GuardedBy("mInMemoryLock")
164 private long mIntervalCompressionMultiplier = DEFAULT_COMPRESSION_STEP;
165
166 // The current ops to which to add statistics.
167 @GuardedBy("mInMemoryLock")
168 private @Nullable HistoricalOps mCurrentHistoricalOps;
169
170 // The time we should write the next snapshot.
171 @GuardedBy("mInMemoryLock")
172 private long mNextPersistDueTimeMillis;
173
174 // How much to offset the history on the next write.
175 @GuardedBy("mInMemoryLock")
176 private long mPendingHistoryOffsetMillis;
177
178 // Object managing persistence (read/write)
179 @GuardedBy("mOnDiskLock")
180 private Persistence mPersistence = new Persistence(mBaseSnapshotInterval,
181 mIntervalCompressionMultiplier);
182
183 HistoricalRegistry(@NonNull Object lock) {
184 mInMemoryLock = lock;
185 if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) {
Svet Ganov80f500c2019-01-19 17:22:45 -0800186 synchronized (mOnDiskLock) {
187 synchronized (mInMemoryLock) {
188 // When starting always adjust history to now.
189 final long lastPersistTimeMills =
190 mPersistence.getLastPersistTimeMillisDLocked();
191 if (lastPersistTimeMills > 0) {
192 mPendingHistoryOffsetMillis =
193 System.currentTimeMillis() - lastPersistTimeMills;
194 }
195 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800196 }
197 }
198 }
199
200 void systemReady(@NonNull ContentResolver resolver) {
201 updateParametersFromSetting(resolver);
202 final Uri uri = Settings.Global.getUriFor(Settings.Global.APPOP_HISTORY_PARAMETERS);
203 resolver.registerContentObserver(uri, false, new ContentObserver(
204 FgThread.getHandler()) {
205 @Override
206 public void onChange(boolean selfChange) {
207 updateParametersFromSetting(resolver);
208 }
209 });
210 }
211
212 private void updateParametersFromSetting(@NonNull ContentResolver resolver) {
213 final String setting = Settings.Global.getString(resolver,
214 Settings.Global.APPOP_HISTORY_PARAMETERS);
215 if (setting == null) {
216 return;
217 }
218 String modeValue = null;
219 String baseSnapshotIntervalValue = null;
220 String intervalMultiplierValue = null;
221 final String[] parameters = setting.split(PARAMETER_DELIMITER);
222 for (String parameter : parameters) {
223 final String[] parts = parameter.split(PARAMETER_ASSIGNMENT);
224 if (parts.length == 2) {
225 final String key = parts[0].trim();
226 switch (key) {
227 case Settings.Global.APPOP_HISTORY_MODE: {
228 modeValue = parts[1].trim();
229 } break;
230 case Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS: {
231 baseSnapshotIntervalValue = parts[1].trim();
232 } break;
233 case Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER: {
234 intervalMultiplierValue = parts[1].trim();
235 } break;
236 default: {
237 Slog.w(LOG_TAG, "Unknown parameter: " + parameter);
238 }
239 }
240 }
241 }
242 if (modeValue != null && baseSnapshotIntervalValue != null
243 && intervalMultiplierValue != null) {
244 try {
245 final int mode = AppOpsManager.parseHistoricalMode(modeValue);
246 final long baseSnapshotInterval = Long.parseLong(baseSnapshotIntervalValue);
247 final int intervalCompressionMultiplier = Integer.parseInt(intervalMultiplierValue);
248 setHistoryParameters(mode, baseSnapshotInterval,intervalCompressionMultiplier);
249 return;
250 } catch (NumberFormatException ignored) {}
251 }
252 Slog.w(LOG_TAG, "Bad value for" + Settings.Global.APPOP_HISTORY_PARAMETERS
253 + "=" + setting + " resetting!");
254 }
255
Svet Ganov80f500c2019-01-19 17:22:45 -0800256 void dump(String prefix, PrintWriter pw, int filterUid,
257 String filterPackage, int filterOp) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800258 synchronized (mOnDiskLock) {
259 synchronized (mInMemoryLock) {
260 pw.println();
261 pw.print(prefix);
262 pw.print("History:");
263
264 pw.print(" mode=");
265 pw.println(AppOpsManager.historicalModeToString(mMode));
266
267 final StringDumpVisitor visitor = new StringDumpVisitor(prefix + " ",
268 pw, filterUid, filterPackage, filterOp);
269 final long nowMillis = System.currentTimeMillis();
270
271 // Dump in memory state first
272 final HistoricalOps currentOps = getUpdatedPendingHistoricalOpsMLocked(
273 nowMillis);
274 makeRelativeToEpochStart(currentOps, nowMillis);
275 currentOps.accept(visitor);
276
277 final List<HistoricalOps> ops = mPersistence.readHistoryDLocked();
278 if (ops != null) {
279 // TODO (bug:122218838): Make sure this is properly dumped
280 final long remainingToFillBatchMillis = mNextPersistDueTimeMillis
281 - nowMillis - mBaseSnapshotInterval;
282 final int opCount = ops.size();
283 for (int i = 0; i < opCount; i++) {
284 final HistoricalOps op = ops.get(i);
285 op.offsetBeginAndEndTime(remainingToFillBatchMillis);
286 makeRelativeToEpochStart(op, nowMillis);
287 op.accept(visitor);
288 }
289 } else {
290 pw.println(" Empty");
291 }
292 }
293 }
294 }
295
296 @HistoricalMode int getMode() {
297 synchronized (mInMemoryLock) {
298 return mMode;
299 }
300 }
301
Svet Ganovaf189e32019-02-15 18:45:29 -0800302 void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
Svet Ganov8455ba22019-01-02 13:05:56 -0800303 @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -0800304 @OpFlags int flags, @NonNull RemoteCallback callback) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800305 final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
306 mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
Svet Ganovaf189e32019-02-15 18:45:29 -0800307 beginTimeMillis, endTimeMillis, flags);
Svet Ganov8455ba22019-01-02 13:05:56 -0800308 final Bundle payload = new Bundle();
309 payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
310 callback.sendResult(payload);
311 }
312
Svet Ganovaf189e32019-02-15 18:45:29 -0800313 void getHistoricalOps(int uid, @NonNull String packageName,
Svet Ganov8455ba22019-01-02 13:05:56 -0800314 @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -0800315 @OpFlags int flags, @NonNull RemoteCallback callback) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800316 final long currentTimeMillis = System.currentTimeMillis();
317 if (endTimeMillis == Long.MAX_VALUE) {
318 endTimeMillis = currentTimeMillis;
319 }
320
321 // Argument times are based off epoch start while our internal store is
322 // based off now, so take this into account.
323 final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0);
324 final long inMemoryAdjEndTimeMillis = Math.max(currentTimeMillis - beginTimeMillis, 0);
325 final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis,
326 inMemoryAdjEndTimeMillis);
327
328 synchronized (mOnDiskLock) {
329 final List<HistoricalOps> pendingWrites;
330 final HistoricalOps currentOps;
Svet Ganovaf189e32019-02-15 18:45:29 -0800331 boolean collectOpsFromDisk;
332
Svet Ganov8455ba22019-01-02 13:05:56 -0800333 synchronized (mInMemoryLock) {
334 currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis);
335 if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis()
336 || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
337 // Some of the current batch falls into the query, so extract that.
338 final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
339 currentOpsCopy.filter(uid, packageName, opNames, inMemoryAdjBeginTimeMillis,
340 inMemoryAdjEndTimeMillis);
341 result.merge(currentOpsCopy);
342 }
343 pendingWrites = new ArrayList<>(mPendingWrites);
344 mPendingWrites.clear();
Svet Ganovaf189e32019-02-15 18:45:29 -0800345 collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis();
Svet Ganov8455ba22019-01-02 13:05:56 -0800346 }
347
348 // If the query was only for in-memory state - done.
Svet Ganovaf189e32019-02-15 18:45:29 -0800349 if (collectOpsFromDisk) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800350 // If there is a write in flight we need to force it now
351 persistPendingHistory(pendingWrites);
352 // Collect persisted state.
353 final long onDiskAndInMemoryOffsetMillis = currentTimeMillis
354 - mNextPersistDueTimeMillis + mBaseSnapshotInterval;
355 final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis
356 - onDiskAndInMemoryOffsetMillis, 0);
357 final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
358 - onDiskAndInMemoryOffsetMillis, 0);
359 mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
Svet Ganovaf189e32019-02-15 18:45:29 -0800360 onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags);
Svet Ganov8455ba22019-01-02 13:05:56 -0800361 }
362
363 // Rebase the result time to be since epoch.
364 result.setBeginAndEndTime(beginTimeMillis, endTimeMillis);
365
366 // Send back the result.
367 final Bundle payload = new Bundle();
368 payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
369 callback.sendResult(payload);
370 }
371 }
372
373 void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
Svet Ganovaf189e32019-02-15 18:45:29 -0800374 @UidState int uidState, @OpFlags int flags) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800375 synchronized (mInMemoryLock) {
376 if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
377 getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
Svet Ganovaf189e32019-02-15 18:45:29 -0800378 .increaseAccessCount(op, uid, packageName, uidState, flags, 1);
Svet Ganov8455ba22019-01-02 13:05:56 -0800379 }
380 }
381 }
382
383 void incrementOpRejected(int op, int uid, @NonNull String packageName,
Svet Ganovaf189e32019-02-15 18:45:29 -0800384 @UidState int uidState, @OpFlags int flags) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800385 synchronized (mInMemoryLock) {
386 if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
387 getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
Svet Ganovaf189e32019-02-15 18:45:29 -0800388 .increaseRejectCount(op, uid, packageName, uidState, flags, 1);
Svet Ganov8455ba22019-01-02 13:05:56 -0800389 }
390 }
391 }
392
393 void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
Svet Ganovaf189e32019-02-15 18:45:29 -0800394 @UidState int uidState, @OpFlags int flags, long increment) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800395 synchronized (mInMemoryLock) {
396 if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
397 getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
Svet Ganovaf189e32019-02-15 18:45:29 -0800398 .increaseAccessDuration(op, uid, packageName, uidState, flags, increment);
Svet Ganov8455ba22019-01-02 13:05:56 -0800399 }
400 }
401 }
402
403 void setHistoryParameters(@HistoricalMode int mode,
404 long baseSnapshotInterval, long intervalCompressionMultiplier) {
405 synchronized (mOnDiskLock) {
406 synchronized (mInMemoryLock) {
407 boolean resampleHistory = false;
408 Slog.i(LOG_TAG, "New history parameters: mode:"
409 + AppOpsManager.historicalModeToString(mMode) + " baseSnapshotInterval:"
410 + baseSnapshotInterval + " intervalCompressionMultiplier:"
411 + intervalCompressionMultiplier);
412 if (mMode != mode) {
413 mMode = mode;
414 if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) {
415 clearHistoryOnDiskLocked();
416 }
417 }
418 if (mBaseSnapshotInterval != baseSnapshotInterval) {
419 mBaseSnapshotInterval = baseSnapshotInterval;
420 resampleHistory = true;
421 }
422 if (mIntervalCompressionMultiplier != intervalCompressionMultiplier) {
423 mIntervalCompressionMultiplier = intervalCompressionMultiplier;
424 resampleHistory = true;
425 }
426 if (resampleHistory) {
427 resampleHistoryOnDiskInMemoryDMLocked(0);
428 }
429 }
430 }
431 }
432
433 void offsetHistory(long offsetMillis) {
434 synchronized (mOnDiskLock) {
435 synchronized (mInMemoryLock) {
436 final List<HistoricalOps> history = mPersistence.readHistoryDLocked();
437 clearHistory();
438 if (history != null) {
439 final int historySize = history.size();
440 for (int i = 0; i < historySize; i++) {
441 final HistoricalOps ops = history.get(i);
442 ops.offsetBeginAndEndTime(offsetMillis);
443 }
444 if (offsetMillis < 0) {
445 pruneFutureOps(history);
446 }
447 mPersistence.persistHistoricalOpsDLocked(history);
448 }
449 }
450 }
451 }
452
453 void addHistoricalOps(HistoricalOps ops) {
454 final List<HistoricalOps> pendingWrites;
455 synchronized (mInMemoryLock) {
456 // The history files start from mBaseSnapshotInterval - take this into account.
457 ops.offsetBeginAndEndTime(mBaseSnapshotInterval);
458 mPendingWrites.offerFirst(ops);
459 pendingWrites = new ArrayList<>(mPendingWrites);
460 mPendingWrites.clear();
461 }
462 persistPendingHistory(pendingWrites);
463 }
464
465 private void resampleHistoryOnDiskInMemoryDMLocked(long offsetMillis) {
466 mPersistence = new Persistence(mBaseSnapshotInterval, mIntervalCompressionMultiplier);
467 offsetHistory(offsetMillis);
468 }
469
470 void resetHistoryParameters() {
471 setHistoryParameters(DEFAULT_MODE, DEFAULT_SNAPSHOT_INTERVAL_MILLIS,
472 DEFAULT_COMPRESSION_STEP);
473 }
474
475 void clearHistory() {
476 synchronized (mOnDiskLock) {
477 clearHistoryOnDiskLocked();
478 }
479 }
480
481 private void clearHistoryOnDiskLocked() {
482 BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
483 synchronized (mInMemoryLock) {
484 mCurrentHistoricalOps = null;
485 mNextPersistDueTimeMillis = System.currentTimeMillis();
486 mPendingWrites.clear();
487 }
488 mPersistence.clearHistoryDLocked();
489 }
490
491 private @NonNull HistoricalOps getUpdatedPendingHistoricalOpsMLocked(long now) {
492 if (mCurrentHistoricalOps != null) {
493 final long remainingTimeMillis = mNextPersistDueTimeMillis - now;
494 if (remainingTimeMillis > mBaseSnapshotInterval) {
495 // If time went backwards we need to push history to the future with the
496 // overflow over our snapshot interval. If time went forward do nothing
497 // as we would naturally push history into the past on the next write.
498 mPendingHistoryOffsetMillis = remainingTimeMillis - mBaseSnapshotInterval;
499 }
500 final long elapsedTimeMillis = mBaseSnapshotInterval - remainingTimeMillis;
501 mCurrentHistoricalOps.setEndTime(elapsedTimeMillis);
502 if (remainingTimeMillis > 0) {
503 if (DEBUG) {
504 Slog.i(LOG_TAG, "Returning current in-memory state");
505 }
506 return mCurrentHistoricalOps;
507 }
508 if (mCurrentHistoricalOps.isEmpty()) {
509 mCurrentHistoricalOps.setBeginAndEndTime(0, 0);
510 mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
511 return mCurrentHistoricalOps;
512 }
513 // The current batch is full, so persist taking into account overdue persist time.
514 mCurrentHistoricalOps.offsetBeginAndEndTime(mBaseSnapshotInterval);
515 mCurrentHistoricalOps.setBeginTime(mCurrentHistoricalOps.getEndTimeMillis()
516 - mBaseSnapshotInterval);
517 final long overdueTimeMillis = Math.abs(remainingTimeMillis);
518 mCurrentHistoricalOps.offsetBeginAndEndTime(overdueTimeMillis);
519 schedulePersistHistoricalOpsMLocked(mCurrentHistoricalOps);
520 }
521 // The current batch is in the future, i.e. not complete yet.
522 mCurrentHistoricalOps = new HistoricalOps(0, 0);
523 mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
524 if (DEBUG) {
525 Slog.i(LOG_TAG, "Returning new in-memory state");
526 }
527 return mCurrentHistoricalOps;
528 }
529
530 private void persistPendingHistory() {
531 final List<HistoricalOps> pendingWrites;
532 synchronized (mOnDiskLock) {
533 synchronized (mInMemoryLock) {
534 pendingWrites = new ArrayList<>(mPendingWrites);
535 mPendingWrites.clear();
536 if (mPendingHistoryOffsetMillis != 0) {
537 resampleHistoryOnDiskInMemoryDMLocked(mPendingHistoryOffsetMillis);
538 mPendingHistoryOffsetMillis = 0;
539 }
540 }
541 persistPendingHistory(pendingWrites);
542 }
543 }
Svet Ganov80f500c2019-01-19 17:22:45 -0800544
Svet Ganov8455ba22019-01-02 13:05:56 -0800545 private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
546 synchronized (mOnDiskLock) {
547 BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
548 if (pendingWrites.isEmpty()) {
549 return;
550 }
551 final int opCount = pendingWrites.size();
552 // Pending writes are offset relative to each other, so take this
553 // into account to persist everything in one shot - single write.
554 for (int i = 0; i < opCount; i++) {
555 final HistoricalOps current = pendingWrites.get(i);
556 if (i > 0) {
557 final HistoricalOps previous = pendingWrites.get(i - 1);
558 current.offsetBeginAndEndTime(previous.getBeginTimeMillis());
559 }
560 }
561 mPersistence.persistHistoricalOpsDLocked(pendingWrites);
562 }
563 }
564
565 private void schedulePersistHistoricalOpsMLocked(@NonNull HistoricalOps ops) {
566 final Message message = PooledLambda.obtainMessage(
567 HistoricalRegistry::persistPendingHistory, HistoricalRegistry.this);
568 message.what = MSG_WRITE_PENDING_HISTORY;
569 BackgroundThread.getHandler().sendMessage(message);
570 mPendingWrites.offerFirst(ops);
571 }
572
573 private static void makeRelativeToEpochStart(@NonNull HistoricalOps ops, long nowMillis) {
574 ops.setBeginAndEndTime(nowMillis - ops.getEndTimeMillis(),
575 nowMillis- ops.getBeginTimeMillis());
576 }
577
578 private void pruneFutureOps(@NonNull List<HistoricalOps> ops) {
579 final int opCount = ops.size();
580 for (int i = opCount - 1; i >= 0; i--) {
581 final HistoricalOps op = ops.get(i);
582 if (op.getEndTimeMillis() <= mBaseSnapshotInterval) {
583 ops.remove(i);
584 } else if (op.getBeginTimeMillis() < mBaseSnapshotInterval) {
585 final double filterScale = (double) (op.getEndTimeMillis() - mBaseSnapshotInterval)
586 / (double) op.getDurationMillis();
587 Persistence.spliceFromBeginning(op, filterScale);
588 }
589 }
590 }
591
592 private static final class Persistence {
593 private static final boolean DEBUG = false;
594
595 private static final String LOG_TAG = Persistence.class.getSimpleName();
596
Svet Ganov8455ba22019-01-02 13:05:56 -0800597 private static final String TAG_HISTORY = "history";
598 private static final String TAG_OPS = "ops";
599 private static final String TAG_UID = "uid";
Svet Ganovaf189e32019-02-15 18:45:29 -0800600 private static final String TAG_PACKAGE = "pkg";
Svet Ganov8455ba22019-01-02 13:05:56 -0800601 private static final String TAG_OP = "op";
Svet Ganovaf189e32019-02-15 18:45:29 -0800602 private static final String TAG_STATE = "st";
Svet Ganov8455ba22019-01-02 13:05:56 -0800603
Svet Ganovaf189e32019-02-15 18:45:29 -0800604 private static final String ATTR_VERSION = "ver";
605 private static final String ATTR_NAME = "na";
606 private static final String ATTR_ACCESS_COUNT = "ac";
607 private static final String ATTR_REJECT_COUNT = "rc";
608 private static final String ATTR_ACCESS_DURATION = "du";
609 private static final String ATTR_BEGIN_TIME = "beg";
610 private static final String ATTR_END_TIME = "end";
611 private static final String ATTR_OVERFLOW = "ov";
Svet Ganov8455ba22019-01-02 13:05:56 -0800612
Svet Ganovaf189e32019-02-15 18:45:29 -0800613 private static final int CURRENT_VERSION = 2;
Svet Ganov8455ba22019-01-02 13:05:56 -0800614
615 private final long mBaseSnapshotInterval;
616 private final long mIntervalCompressionMultiplier;
617
618 Persistence(long baseSnapshotInterval, long intervalCompressionMultiplier) {
619 mBaseSnapshotInterval = baseSnapshotInterval;
620 mIntervalCompressionMultiplier = intervalCompressionMultiplier;
621 }
622
623 private final AtomicDirectory mHistoricalAppOpsDir = new AtomicDirectory(
Svet Ganov80f500c2019-01-19 17:22:45 -0800624 new File(new File(Environment.getDataSystemDirectory(), "appops"), "history"));
Svet Ganov8455ba22019-01-02 13:05:56 -0800625
626 private File generateFile(@NonNull File baseDir, int depth) {
627 final long globalBeginMillis = computeGlobalIntervalBeginMillis(depth);
628 return new File(baseDir, Long.toString(globalBeginMillis) + HISTORY_FILE_SUFFIX);
629 }
630
631 void clearHistoryDLocked() {
632 mHistoricalAppOpsDir.delete();
633 }
634
635 void persistHistoricalOpsDLocked(@NonNull List<HistoricalOps> ops) {
636 if (DEBUG) {
637 Slog.i(LOG_TAG, "Persisting ops:\n" + opsToDebugString(ops));
638 enforceOpsWellFormed(ops);
639 }
640 try {
641 final File newBaseDir = mHistoricalAppOpsDir.startWrite();
642 final File oldBaseDir = mHistoricalAppOpsDir.getBackupDirectory();
Svet Ganov80f500c2019-01-19 17:22:45 -0800643 final HistoricalFilesInvariant filesInvariant;
644 if (DEBUG) {
645 filesInvariant = new HistoricalFilesInvariant();
646 filesInvariant.startTracking(oldBaseDir);
647 }
648 final Set<String> oldFileNames = getHistoricalFileNames(oldBaseDir);
649 handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops,
650 oldFileNames, 0);
651 if (DEBUG) {
652 filesInvariant.stopTracking(newBaseDir);
653 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800654 mHistoricalAppOpsDir.finishWrite();
655 } catch (Throwable t) {
Svet Ganov80f500c2019-01-19 17:22:45 -0800656 wtf("Failed to write historical app ops, restoring backup", t, null);
Svet Ganov8455ba22019-01-02 13:05:56 -0800657 mHistoricalAppOpsDir.failWrite();
658 }
659 }
660
661 @Nullable List<HistoricalOps> readHistoryRawDLocked() {
662 return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/,
663 null /*filterPackageName*/, null /*filterOpNames*/,
Svet Ganovaf189e32019-02-15 18:45:29 -0800664 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/,
665 AppOpsManager.OP_FLAGS_ALL);
Svet Ganov8455ba22019-01-02 13:05:56 -0800666 }
667
668 @Nullable List<HistoricalOps> readHistoryDLocked() {
669 final List<HistoricalOps> result = readHistoryRawDLocked();
670 // Take into account in memory state duration.
671 if (result != null) {
672 final int opCount = result.size();
673 for (int i = 0; i < opCount; i++) {
674 result.get(i).offsetBeginAndEndTime(mBaseSnapshotInterval);
675 }
676 }
677 return result;
678 }
679
680 long getLastPersistTimeMillisDLocked() {
Svet Ganov80f500c2019-01-19 17:22:45 -0800681 File baseDir = null;
Svet Ganov8455ba22019-01-02 13:05:56 -0800682 try {
Svet Ganov80f500c2019-01-19 17:22:45 -0800683 baseDir = mHistoricalAppOpsDir.startRead();
684 final File[] files = baseDir.listFiles();
685 if (files != null && files.length > 0) {
686 final Set<File> historyFiles = new ArraySet<>();
687 Collections.addAll(historyFiles, files);
688 for (int i = 0;; i++) {
689 final File file = generateFile(baseDir, i);
690 if (historyFiles.contains(file)) {
691 return file.lastModified();
692 }
693 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800694 }
695 mHistoricalAppOpsDir.finishRead();
Svet Ganov80f500c2019-01-19 17:22:45 -0800696 } catch (Throwable e) {
697 wtf("Error reading historical app ops. Deleting history.", e, baseDir);
Svet Ganov8455ba22019-01-02 13:05:56 -0800698 mHistoricalAppOpsDir.delete();
699 }
700 return 0;
701 }
702
703 private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps,
704 int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
Svet Ganovaf189e32019-02-15 18:45:29 -0800705 long filterBeingMillis, long filterEndMillis, @OpFlags int filterFlags) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800706 final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid,
Svet Ganovaf189e32019-02-15 18:45:29 -0800707 filterPackageName, filterOpNames, filterBeingMillis, filterEndMillis,
708 filterFlags);
Svet Ganov8455ba22019-01-02 13:05:56 -0800709 if (readOps != null) {
710 final int readCount = readOps.size();
711 for (int i = 0; i < readCount; i++) {
712 final HistoricalOps readOp = readOps.get(i);
713 currentOps.merge(readOp);
714 }
715 }
716 }
717
718 private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(
719 int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
Svet Ganovaf189e32019-02-15 18:45:29 -0800720 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags) {
Svet Ganov80f500c2019-01-19 17:22:45 -0800721 File baseDir = null;
Svet Ganov8455ba22019-01-02 13:05:56 -0800722 try {
Svet Ganov80f500c2019-01-19 17:22:45 -0800723 baseDir = mHistoricalAppOpsDir.startRead();
724 final HistoricalFilesInvariant filesInvariant;
725 if (DEBUG) {
726 filesInvariant = new HistoricalFilesInvariant();
727 filesInvariant.startTracking(baseDir);
Svet Ganov8455ba22019-01-02 13:05:56 -0800728 }
Svet Ganov80f500c2019-01-19 17:22:45 -0800729 final Set<String> historyFiles = getHistoricalFileNames(baseDir);
Svet Ganov8455ba22019-01-02 13:05:56 -0800730 final long[] globalContentOffsetMillis = {0};
731 final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
732 baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -0800733 filterEndTimeMillis, filterFlags, globalContentOffsetMillis,
734 null /*outOps*/, 0 /*depth*/, historyFiles);
Svet Ganov80f500c2019-01-19 17:22:45 -0800735 if (DEBUG) {
736 filesInvariant.stopTracking(baseDir);
737 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800738 mHistoricalAppOpsDir.finishRead();
739 return ops;
Svet Ganov80f500c2019-01-19 17:22:45 -0800740 } catch (Throwable t) {
741 wtf("Error reading historical app ops. Deleting history.", t, baseDir);
Svet Ganov8455ba22019-01-02 13:05:56 -0800742 mHistoricalAppOpsDir.delete();
743 }
744 return null;
745 }
746
747 private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked(
748 @NonNull File baseDir, int filterUid, @NonNull String filterPackageName,
749 @Nullable String[] filterOpNames, long filterBeginTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -0800750 long filterEndTimeMillis, @OpFlags int filterFlags,
751 @NonNull long[] globalContentOffsetMillis,
Svet Ganov8455ba22019-01-02 13:05:56 -0800752 @Nullable LinkedList<HistoricalOps> outOps, int depth,
Svet Ganov80f500c2019-01-19 17:22:45 -0800753 @NonNull Set<String> historyFiles)
Svet Ganov8455ba22019-01-02 13:05:56 -0800754 throws IOException, XmlPullParserException {
755 final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
756 depth) * mBaseSnapshotInterval;
757 final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
758 depth + 1) * mBaseSnapshotInterval;
759
760 filterBeginTimeMillis = Math.max(filterBeginTimeMillis - previousIntervalEndMillis, 0);
761 filterEndTimeMillis = filterEndTimeMillis - previousIntervalEndMillis;
762
763 // Read historical data at this level
764 final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir,
765 previousIntervalEndMillis, currentIntervalEndMillis, filterUid,
766 filterPackageName, filterOpNames, filterBeginTimeMillis, filterEndTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -0800767 filterFlags, globalContentOffsetMillis, depth, historyFiles);
Svet Ganov8455ba22019-01-02 13:05:56 -0800768
769 // Empty is a special signal to stop diving
770 if (readOps != null && readOps.isEmpty()) {
771 return outOps;
772 }
773
774 // Collect older historical data from subsequent levels
775 outOps = collectHistoricalOpsRecursiveDLocked(
776 baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -0800777 filterEndTimeMillis, filterFlags, globalContentOffsetMillis, outOps, depth + 1,
Svet Ganov8455ba22019-01-02 13:05:56 -0800778 historyFiles);
779
780 // Make older historical data relative to the current historical level
781 if (outOps != null) {
782 final int opCount = outOps.size();
783 for (int i = 0; i < opCount; i++) {
784 final HistoricalOps collectedOp = outOps.get(i);
785 collectedOp.offsetBeginAndEndTime(currentIntervalEndMillis);
786 }
787 }
788
789 if (readOps != null) {
790 if (outOps == null) {
791 outOps = new LinkedList<>();
792 }
793 // Add the read ops to output
794 final int opCount = readOps.size();
795 for (int i = opCount - 1; i >= 0; i--) {
796 outOps.offerFirst(readOps.get(i));
797 }
798 }
799
800 return outOps;
801 }
802
Svet Ganov8455ba22019-01-02 13:05:56 -0800803 private void handlePersistHistoricalOpsRecursiveDLocked(@NonNull File newBaseDir,
Svet Ganov80f500c2019-01-19 17:22:45 -0800804 @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps,
805 @NonNull Set<String> oldFileNames, int depth)
Svet Ganov8455ba22019-01-02 13:05:56 -0800806 throws IOException, XmlPullParserException {
807 final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
808 depth) * mBaseSnapshotInterval;
809 final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
810 depth + 1) * mBaseSnapshotInterval;
811
812 if (passedOps == null || passedOps.isEmpty()) {
Svet Ganov80f500c2019-01-19 17:22:45 -0800813 if (!oldFileNames.isEmpty()) {
814 // If there is an old file we need to copy it over to the new state.
815 final File oldFile = generateFile(oldBaseDir, depth);
816 if (oldFileNames.remove(oldFile.getName())) {
817 final File newFile = generateFile(newBaseDir, depth);
818 Files.createLink(newFile.toPath(), oldFile.toPath());
819 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800820 handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
Svet Ganov80f500c2019-01-19 17:22:45 -0800821 passedOps, oldFileNames, depth + 1);
Svet Ganov8455ba22019-01-02 13:05:56 -0800822 }
823 return;
824 }
825
826 if (DEBUG) {
827 enforceOpsWellFormed(passedOps);
828 }
829
830 // Normalize passed ops time to be based off this interval start
831 final int passedOpCount = passedOps.size();
832 for (int i = 0; i < passedOpCount; i++) {
833 final HistoricalOps passedOp = passedOps.get(i);
834 passedOp.offsetBeginAndEndTime(-previousIntervalEndMillis);
835 }
836
837 if (DEBUG) {
838 enforceOpsWellFormed(passedOps);
839 }
840
841 // Read persisted ops for this interval
842 final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir,
843 previousIntervalEndMillis, currentIntervalEndMillis,
844 Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/,
845 null /*filterOpNames*/, Long.MIN_VALUE /*filterBeginTimeMillis*/,
Svet Ganovaf189e32019-02-15 18:45:29 -0800846 Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL,
847 null, depth, null /*historyFiles*/);
Svet Ganov8455ba22019-01-02 13:05:56 -0800848
849 if (DEBUG) {
850 enforceOpsWellFormed(existingOps);
851 }
852
853 // Offset existing ops to account for elapsed time
Svet Ganovaf189e32019-02-15 18:45:29 -0800854 if (existingOps != null) {
855 final int existingOpCount = existingOps.size();
856 if (existingOpCount > 0) {
857 // Compute elapsed time
858 final long elapsedTimeMillis = passedOps.get(passedOps.size() - 1)
Svet Ganov8455ba22019-01-02 13:05:56 -0800859 .getEndTimeMillis();
Svet Ganovaf189e32019-02-15 18:45:29 -0800860 for (int i = 0; i < existingOpCount; i++) {
861 final HistoricalOps existingOp = existingOps.get(i);
862 existingOp.offsetBeginAndEndTime(elapsedTimeMillis);
863 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800864 }
865 }
866
867 if (DEBUG) {
868 enforceOpsWellFormed(existingOps);
869 }
870
871 final long slotDurationMillis = previousIntervalEndMillis;
872
873 // Consolidate passed ops at the current slot duration ensuring each snapshot is
874 // full. To achieve this we put all passed and existing ops in a list and will
875 // merge them to ensure each represents a snapshot at the current granularity.
Svet Ganovaf189e32019-02-15 18:45:29 -0800876 final List<HistoricalOps> allOps = new LinkedList<>(passedOps);
877 if (existingOps != null) {
878 allOps.addAll(existingOps);
879 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800880
881 if (DEBUG) {
882 enforceOpsWellFormed(allOps);
883 }
884
885 // Compute ops to persist and overflow ops
886 List<HistoricalOps> persistedOps = null;
887 List<HistoricalOps> overflowedOps = null;
888
889 // We move a snapshot into the next level only if the start time is
890 // after the end of the current interval. This avoids rewriting all
891 // files to propagate the information added to the history on every
892 // iteration. Instead, we would rewrite the next level file only if
893 // an entire snapshot from the previous level is being propagated.
894 // The trade off is that we need to store how much the last snapshot
895 // of the current interval overflows past the interval end. We write
896 // the overflow data to avoid parsing all snapshots on query.
897 long intervalOverflowMillis = 0;
898 final int opCount = allOps.size();
899 for (int i = 0; i < opCount; i++) {
900 final HistoricalOps op = allOps.get(i);
901 final HistoricalOps persistedOp;
902 final HistoricalOps overflowedOp;
903 if (op.getEndTimeMillis() <= currentIntervalEndMillis) {
904 persistedOp = op;
905 overflowedOp = null;
906 } else if (op.getBeginTimeMillis() < currentIntervalEndMillis) {
907 persistedOp = op;
908 intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
909 if (intervalOverflowMillis > previousIntervalEndMillis) {
910 final double splitScale = (double) intervalOverflowMillis
911 / op.getDurationMillis();
912 overflowedOp = spliceFromEnd(op, splitScale);
913 intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
914 } else {
915 overflowedOp = null;
916 }
917 } else {
918 persistedOp = null;
919 overflowedOp = op;
920 }
921 if (persistedOp != null) {
922 if (persistedOps == null) {
923 persistedOps = new ArrayList<>();
924 }
925 persistedOps.add(persistedOp);
926 }
927 if (overflowedOp != null) {
928 if (overflowedOps == null) {
929 overflowedOps = new ArrayList<>();
930 }
931 overflowedOps.add(overflowedOp);
932 }
933 }
934
935 if (DEBUG) {
936 enforceOpsWellFormed(persistedOps);
937 enforceOpsWellFormed(overflowedOps);
938 }
939
Svet Ganov80f500c2019-01-19 17:22:45 -0800940 final File newFile = generateFile(newBaseDir, depth);
941 oldFileNames.remove(newFile.getName());
942
Svet Ganov8455ba22019-01-02 13:05:56 -0800943 if (persistedOps != null) {
944 normalizeSnapshotForSlotDuration(persistedOps, slotDurationMillis);
Svet Ganov8455ba22019-01-02 13:05:56 -0800945 writeHistoricalOpsDLocked(persistedOps, intervalOverflowMillis, newFile);
946 if (DEBUG) {
947 Slog.i(LOG_TAG, "Persisted at depth: " + depth
948 + " ops:\n" + opsToDebugString(persistedOps));
949 enforceOpsWellFormed(persistedOps);
950 }
951 }
952
953 handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
Svet Ganov80f500c2019-01-19 17:22:45 -0800954 overflowedOps, oldFileNames, depth + 1);
Svet Ganov8455ba22019-01-02 13:05:56 -0800955 }
956
Svet Ganovaf189e32019-02-15 18:45:29 -0800957 private @Nullable List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
958 long intervalBeginMillis, long intervalEndMillis,
959 int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
960 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags,
Svet Ganov8455ba22019-01-02 13:05:56 -0800961 @Nullable long[] cumulativeOverflowMillis, int depth,
Svet Ganov80f500c2019-01-19 17:22:45 -0800962 @NonNull Set<String> historyFiles)
Svet Ganov8455ba22019-01-02 13:05:56 -0800963 throws IOException, XmlPullParserException {
964 final File file = generateFile(baseDir, depth);
965 if (historyFiles != null) {
Svet Ganov80f500c2019-01-19 17:22:45 -0800966 historyFiles.remove(file.getName());
Svet Ganov8455ba22019-01-02 13:05:56 -0800967 }
968 if (filterBeginTimeMillis >= filterEndTimeMillis
969 || filterEndTimeMillis < intervalBeginMillis) {
970 // Don't go deeper
971 return Collections.emptyList();
972 }
973 if (filterBeginTimeMillis >= (intervalEndMillis
974 + ((intervalEndMillis - intervalBeginMillis) / mIntervalCompressionMultiplier)
975 + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0))
976 || !file.exists()) {
977 if (historyFiles == null || historyFiles.isEmpty()) {
978 // Don't go deeper
979 return Collections.emptyList();
980 } else {
981 // Keep diving
982 return null;
983 }
984 }
985 return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterOpNames,
Svet Ganovaf189e32019-02-15 18:45:29 -0800986 filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
987 cumulativeOverflowMillis);
Svet Ganov8455ba22019-01-02 13:05:56 -0800988 }
989
990 private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file,
991 int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
Svet Ganovaf189e32019-02-15 18:45:29 -0800992 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags,
Svet Ganov8455ba22019-01-02 13:05:56 -0800993 @Nullable long[] cumulativeOverflowMillis)
994 throws IOException, XmlPullParserException {
995 if (DEBUG) {
996 Slog.i(LOG_TAG, "Reading ops from:" + file);
997 }
998 List<HistoricalOps> allOps = null;
999 try (FileInputStream stream = new FileInputStream(file)) {
1000 final XmlPullParser parser = Xml.newPullParser();
1001 parser.setInput(stream, StandardCharsets.UTF_8.name());
1002 XmlUtils.beginDocument(parser, TAG_HISTORY);
Svet Ganovaf189e32019-02-15 18:45:29 -08001003
1004 // We haven't released version 1 and have more detailed
1005 // accounting - just nuke the current state
1006 final int version = XmlUtils.readIntAttribute(parser, ATTR_VERSION);
1007 if (CURRENT_VERSION == 2 && version < CURRENT_VERSION) {
1008 throw new IllegalStateException("Dropping unsupported history "
1009 + "version 1 for file:" + file);
1010 }
1011
Svet Ganov8455ba22019-01-02 13:05:56 -08001012 final long overflowMillis = XmlUtils.readLongAttribute(parser, ATTR_OVERFLOW, 0);
1013 final int depth = parser.getDepth();
1014 while (XmlUtils.nextElementWithin(parser, depth)) {
1015 if (TAG_OPS.equals(parser.getName())) {
1016 final HistoricalOps ops = readeHistoricalOpsDLocked(parser,
1017 filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -08001018 filterEndTimeMillis, filterFlags, cumulativeOverflowMillis);
Svet Ganov8455ba22019-01-02 13:05:56 -08001019 if (ops == null) {
1020 continue;
1021 }
1022 if (ops.isEmpty()) {
1023 XmlUtils.skipCurrentTag(parser);
1024 continue;
1025 }
1026 if (allOps == null) {
1027 allOps = new ArrayList<>();
1028 }
1029 allOps.add(ops);
1030 }
1031 }
1032 if (cumulativeOverflowMillis != null) {
1033 cumulativeOverflowMillis[0] += overflowMillis;
1034 }
1035 } catch (FileNotFoundException e) {
1036 Slog.i(LOG_TAG, "No history file: " + file.getName());
1037 return Collections.emptyList();
1038 }
1039 if (DEBUG) {
1040 if (allOps != null) {
1041 Slog.i(LOG_TAG, "Read from file: " + file + "ops:\n"
1042 + opsToDebugString(allOps));
1043 enforceOpsWellFormed(allOps);
1044 }
1045 }
1046 return allOps;
1047 }
1048
1049 private @Nullable HistoricalOps readeHistoricalOpsDLocked(
1050 @NonNull XmlPullParser parser, int filterUid, @Nullable String filterPackageName,
1051 @Nullable String[] filterOpNames, long filterBeginTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -08001052 long filterEndTimeMillis, @OpFlags int filterFlags,
1053 @Nullable long[] cumulativeOverflowMillis)
Svet Ganov8455ba22019-01-02 13:05:56 -08001054 throws IOException, XmlPullParserException {
1055 final long beginTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_BEGIN_TIME, 0)
1056 + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
1057 final long endTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_END_TIME, 0)
1058 + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
1059 // Keep reading as subsequent records may start matching
1060 if (filterEndTimeMillis < beginTimeMillis) {
1061 return null;
1062 }
1063 // Stop reading as subsequent records will not match
1064 if (filterBeginTimeMillis > endTimeMillis) {
1065 return new HistoricalOps(0, 0);
1066 }
1067 final long filteredBeginTimeMillis = Math.max(beginTimeMillis, filterBeginTimeMillis);
1068 final long filteredEndTimeMillis = Math.min(endTimeMillis, filterEndTimeMillis);
Svet Ganovaf189e32019-02-15 18:45:29 -08001069 // // Keep reading as subsequent records may start matching
1070 // if (filteredEndTimeMillis - filterBeginTimeMillis <= 0) {
1071 // return null;
1072 // }
Svet Ganov8455ba22019-01-02 13:05:56 -08001073 final double filterScale = (double) (filteredEndTimeMillis - filteredBeginTimeMillis)
1074 / (double) (endTimeMillis - beginTimeMillis);
1075 HistoricalOps ops = null;
1076 final int depth = parser.getDepth();
1077 while (XmlUtils.nextElementWithin(parser, depth)) {
1078 if (TAG_UID.equals(parser.getName())) {
1079 final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser,
Svet Ganovaf189e32019-02-15 18:45:29 -08001080 filterUid, filterPackageName, filterOpNames, filterFlags, filterScale);
Svet Ganov8455ba22019-01-02 13:05:56 -08001081 if (ops == null) {
1082 ops = returnedOps;
1083 }
1084 }
1085 }
1086 if (ops != null) {
1087 ops.setBeginAndEndTime(filteredBeginTimeMillis, filteredEndTimeMillis);
1088 }
1089 return ops;
1090 }
1091
1092 private @Nullable HistoricalOps readHistoricalUidOpsDLocked(
1093 @Nullable HistoricalOps ops, @NonNull XmlPullParser parser, int filterUid,
1094 @Nullable String filterPackageName, @Nullable String[] filterOpNames,
Svet Ganovaf189e32019-02-15 18:45:29 -08001095 @OpFlags int filterFlags, double filterScale)
1096 throws IOException, XmlPullParserException {
Svet Ganov8455ba22019-01-02 13:05:56 -08001097 final int uid = XmlUtils.readIntAttribute(parser, ATTR_NAME);
1098 if (filterUid != Process.INVALID_UID && filterUid != uid) {
1099 XmlUtils.skipCurrentTag(parser);
1100 return null;
1101 }
1102 final int depth = parser.getDepth();
1103 while (XmlUtils.nextElementWithin(parser, depth)) {
1104 if (TAG_PACKAGE.equals(parser.getName())) {
1105 final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops,
Svet Ganovaf189e32019-02-15 18:45:29 -08001106 uid, parser, filterPackageName, filterOpNames, filterFlags,
1107 filterScale);
Svet Ganov8455ba22019-01-02 13:05:56 -08001108 if (ops == null) {
1109 ops = returnedOps;
1110 }
1111 }
1112 }
1113 return ops;
1114 }
1115
1116 private @Nullable HistoricalOps readHistoricalPackageOpsDLocked(
1117 @Nullable HistoricalOps ops, int uid, @NonNull XmlPullParser parser,
1118 @Nullable String filterPackageName, @Nullable String[] filterOpNames,
Svet Ganovaf189e32019-02-15 18:45:29 -08001119 @OpFlags int filterFlags, double filterScale)
1120 throws IOException, XmlPullParserException {
Svet Ganov8455ba22019-01-02 13:05:56 -08001121 final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME);
1122 if (filterPackageName != null && !filterPackageName.equals(packageName)) {
1123 XmlUtils.skipCurrentTag(parser);
1124 return null;
1125 }
1126 final int depth = parser.getDepth();
1127 while (XmlUtils.nextElementWithin(parser, depth)) {
1128 if (TAG_OP.equals(parser.getName())) {
1129 final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid,
Svet Ganovaf189e32019-02-15 18:45:29 -08001130 packageName, parser, filterOpNames, filterFlags, filterScale);
Svet Ganov8455ba22019-01-02 13:05:56 -08001131 if (ops == null) {
1132 ops = returnedOps;
1133 }
1134 }
1135 }
1136 return ops;
1137 }
1138
1139 private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops,
1140 int uid, String packageName, @NonNull XmlPullParser parser,
Svet Ganovaf189e32019-02-15 18:45:29 -08001141 @Nullable String[] filterOpNames, @OpFlags int filterFlags, double filterScale)
Svet Ganov8455ba22019-01-02 13:05:56 -08001142 throws IOException, XmlPullParserException {
1143 final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME);
1144 if (filterOpNames != null && !ArrayUtils.contains(filterOpNames,
Svet Ganov65f1b9e2019-01-17 19:19:40 -08001145 AppOpsManager.opToPublicName(op))) {
Svet Ganov8455ba22019-01-02 13:05:56 -08001146 XmlUtils.skipCurrentTag(parser);
1147 return null;
1148 }
1149 final int depth = parser.getDepth();
1150 while (XmlUtils.nextElementWithin(parser, depth)) {
1151 if (TAG_STATE.equals(parser.getName())) {
Svet Ganovaf189e32019-02-15 18:45:29 -08001152 final HistoricalOps returnedOps = readStateDLocked(ops, uid,
1153 packageName, op, parser, filterFlags, filterScale);
Svet Ganov8455ba22019-01-02 13:05:56 -08001154 if (ops == null) {
1155 ops = returnedOps;
1156 }
1157 }
1158 }
1159 return ops;
1160 }
1161
Svet Ganovaf189e32019-02-15 18:45:29 -08001162 private @Nullable HistoricalOps readStateDLocked(@Nullable HistoricalOps ops,
Svet Ganov8455ba22019-01-02 13:05:56 -08001163 int uid, String packageName, int op, @NonNull XmlPullParser parser,
Svet Ganovaf189e32019-02-15 18:45:29 -08001164 @OpFlags int filterFlags, double filterScale) throws IOException {
1165 final long key = XmlUtils.readLongAttribute(parser, ATTR_NAME);
1166 final int flags = AppOpsManager.extractFlagsFromKey(key) & filterFlags;
1167 if (flags == 0) {
1168 return null;
1169 }
1170 final int uidState = AppOpsManager.extractUidStateFromKey(key);
Svet Ganov8455ba22019-01-02 13:05:56 -08001171 long accessCount = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_COUNT, 0);
1172 if (accessCount > 0) {
1173 if (!Double.isNaN(filterScale)) {
1174 accessCount = (long) HistoricalOps.round(
1175 (double) accessCount * filterScale);
1176 }
1177 if (ops == null) {
1178 ops = new HistoricalOps(0, 0);
1179 }
Svet Ganovaf189e32019-02-15 18:45:29 -08001180 ops.increaseAccessCount(op, uid, packageName, uidState, flags, accessCount);
Svet Ganov8455ba22019-01-02 13:05:56 -08001181 }
1182 long rejectCount = XmlUtils.readLongAttribute(parser, ATTR_REJECT_COUNT, 0);
1183 if (rejectCount > 0) {
1184 if (!Double.isNaN(filterScale)) {
1185 rejectCount = (long) HistoricalOps.round(
1186 (double) rejectCount * filterScale);
1187 }
1188 if (ops == null) {
1189 ops = new HistoricalOps(0, 0);
1190 }
Svet Ganovaf189e32019-02-15 18:45:29 -08001191 ops.increaseRejectCount(op, uid, packageName, uidState, flags, rejectCount);
Svet Ganov8455ba22019-01-02 13:05:56 -08001192 }
1193 long accessDuration = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_DURATION, 0);
1194 if (accessDuration > 0) {
1195 if (!Double.isNaN(filterScale)) {
1196 accessDuration = (long) HistoricalOps.round(
1197 (double) accessDuration * filterScale);
1198 }
1199 if (ops == null) {
1200 ops = new HistoricalOps(0, 0);
1201 }
Svet Ganovaf189e32019-02-15 18:45:29 -08001202 ops.increaseAccessDuration(op, uid, packageName, uidState, flags, accessDuration);
Svet Ganov8455ba22019-01-02 13:05:56 -08001203 }
1204 return ops;
1205 }
1206
1207 private void writeHistoricalOpsDLocked(@Nullable List<HistoricalOps> allOps,
1208 long intervalOverflowMillis, @NonNull File file) throws IOException {
1209 final FileOutputStream output = mHistoricalAppOpsDir.openWrite(file);
1210 try {
1211 final XmlSerializer serializer = Xml.newSerializer();
1212 serializer.setOutput(output, StandardCharsets.UTF_8.name());
1213 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
1214 true);
1215 serializer.startDocument(null, true);
1216 serializer.startTag(null, TAG_HISTORY);
1217 serializer.attribute(null, ATTR_VERSION, String.valueOf(CURRENT_VERSION));
1218 if (intervalOverflowMillis != 0) {
1219 serializer.attribute(null, ATTR_OVERFLOW,
1220 Long.toString(intervalOverflowMillis));
1221 }
1222 if (allOps != null) {
1223 final int opsCount = allOps.size();
1224 for (int i = 0; i < opsCount; i++) {
1225 final HistoricalOps ops = allOps.get(i);
1226 writeHistoricalOpDLocked(ops, serializer);
1227 }
1228 }
1229 serializer.endTag(null, TAG_HISTORY);
1230 serializer.endDocument();
1231 mHistoricalAppOpsDir.closeWrite(output);
1232 } catch (IOException e) {
1233 mHistoricalAppOpsDir.failWrite(output);
1234 throw e;
1235 }
1236 }
1237
1238 private void writeHistoricalOpDLocked(@NonNull HistoricalOps ops,
1239 @NonNull XmlSerializer serializer) throws IOException {
1240 serializer.startTag(null, TAG_OPS);
1241 serializer.attribute(null, ATTR_BEGIN_TIME, Long.toString(ops.getBeginTimeMillis()));
1242 serializer.attribute(null, ATTR_END_TIME, Long.toString(ops.getEndTimeMillis()));
1243 final int uidCount = ops.getUidCount();
1244 for (int i = 0; i < uidCount; i++) {
1245 final HistoricalUidOps uidOp = ops.getUidOpsAt(i);
1246 writeHistoricalUidOpsDLocked(uidOp, serializer);
1247 }
1248 serializer.endTag(null, TAG_OPS);
1249 }
1250
1251 private void writeHistoricalUidOpsDLocked(@NonNull HistoricalUidOps uidOps,
1252 @NonNull XmlSerializer serializer) throws IOException {
1253 serializer.startTag(null, TAG_UID);
1254 serializer.attribute(null, ATTR_NAME, Integer.toString(uidOps.getUid()));
1255 final int packageCount = uidOps.getPackageCount();
1256 for (int i = 0; i < packageCount; i++) {
1257 final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(i);
1258 writeHistoricalPackageOpsDLocked(packageOps, serializer);
1259 }
1260 serializer.endTag(null, TAG_UID);
1261 }
1262
1263 private void writeHistoricalPackageOpsDLocked(@NonNull HistoricalPackageOps packageOps,
1264 @NonNull XmlSerializer serializer) throws IOException {
1265 serializer.startTag(null, TAG_PACKAGE);
1266 serializer.attribute(null, ATTR_NAME, packageOps.getPackageName());
1267 final int opCount = packageOps.getOpCount();
1268 for (int i = 0; i < opCount; i++) {
1269 final HistoricalOp op = packageOps.getOpAt(i);
1270 writeHistoricalOpDLocked(op, serializer);
1271 }
1272 serializer.endTag(null, TAG_PACKAGE);
1273 }
1274
1275 private void writeHistoricalOpDLocked(@NonNull HistoricalOp op,
1276 @NonNull XmlSerializer serializer) throws IOException {
Svet Ganovaf189e32019-02-15 18:45:29 -08001277 final LongSparseArray keys = op.collectKeys();
1278 if (keys == null || keys.size() <= 0) {
1279 return;
1280 }
Svet Ganov8455ba22019-01-02 13:05:56 -08001281 serializer.startTag(null, TAG_OP);
1282 serializer.attribute(null, ATTR_NAME, Integer.toString(op.getOpCode()));
Svet Ganovaf189e32019-02-15 18:45:29 -08001283 final int keyCount = keys.size();
1284 for (int i = 0; i < keyCount; i++) {
1285 writeStateOnLocked(op, keys.keyAt(i), serializer);
Svet Ganov8455ba22019-01-02 13:05:56 -08001286 }
1287 serializer.endTag(null, TAG_OP);
1288 }
1289
Svet Ganovaf189e32019-02-15 18:45:29 -08001290 private void writeStateOnLocked(@NonNull HistoricalOp op, long key,
Svet Ganov8455ba22019-01-02 13:05:56 -08001291 @NonNull XmlSerializer serializer) throws IOException {
Svet Ganovaf189e32019-02-15 18:45:29 -08001292 final int uidState = AppOpsManager.extractUidStateFromKey(key);
1293 final int flags = AppOpsManager.extractFlagsFromKey(key);
1294
1295 final long accessCount = op.getAccessCount(uidState, uidState, flags);
1296 final long rejectCount = op.getRejectCount(uidState, uidState, flags);
1297 final long accessDuration = op.getAccessDuration(uidState, uidState, flags);
1298
1299 if (accessCount <= 0 && rejectCount <= 0 && accessDuration <= 0) {
Svet Ganov8455ba22019-01-02 13:05:56 -08001300 return;
1301 }
Svet Ganovaf189e32019-02-15 18:45:29 -08001302
Svet Ganov8455ba22019-01-02 13:05:56 -08001303 serializer.startTag(null, TAG_STATE);
Svet Ganovaf189e32019-02-15 18:45:29 -08001304 serializer.attribute(null, ATTR_NAME, Long.toString(key));
Svet Ganov8455ba22019-01-02 13:05:56 -08001305 if (accessCount > 0) {
1306 serializer.attribute(null, ATTR_ACCESS_COUNT, Long.toString(accessCount));
1307 }
1308 if (rejectCount > 0) {
1309 serializer.attribute(null, ATTR_REJECT_COUNT, Long.toString(rejectCount));
1310 }
1311 if (accessDuration > 0) {
1312 serializer.attribute(null, ATTR_ACCESS_DURATION, Long.toString(accessDuration));
1313 }
1314 serializer.endTag(null, TAG_STATE);
1315 }
1316
1317 private static void enforceOpsWellFormed(@NonNull List<HistoricalOps> ops) {
1318 if (ops == null) {
1319 return;
1320 }
1321 HistoricalOps previous;
1322 HistoricalOps current = null;
1323 final int opsCount = ops.size();
1324 for (int i = 0; i < opsCount; i++) {
1325 previous = current;
1326 current = ops.get(i);
1327 if (current.isEmpty()) {
1328 throw new IllegalStateException("Empty ops:\n"
1329 + opsToDebugString(ops));
1330 }
1331 if (current.getEndTimeMillis() < current.getBeginTimeMillis()) {
1332 throw new IllegalStateException("Begin after end:\n"
1333 + opsToDebugString(ops));
1334 }
1335 if (previous != null) {
1336 if (previous.getEndTimeMillis() > current.getBeginTimeMillis()) {
1337 throw new IllegalStateException("Intersecting ops:\n"
1338 + opsToDebugString(ops));
1339 }
1340 if (previous.getBeginTimeMillis() > current.getBeginTimeMillis()) {
1341 throw new IllegalStateException("Non increasing ops:\n"
1342 + opsToDebugString(ops));
1343 }
1344 }
1345 }
1346 }
1347
1348 private long computeGlobalIntervalBeginMillis(int depth) {
1349 long beginTimeMillis = 0;
1350 for (int i = 0; i < depth + 1; i++) {
1351 beginTimeMillis += Math.pow(mIntervalCompressionMultiplier, i);
1352 }
1353 return beginTimeMillis * mBaseSnapshotInterval;
1354 }
1355
1356 private static @NonNull HistoricalOps spliceFromEnd(@NonNull HistoricalOps ops,
1357 double spliceRatio) {
1358 if (DEBUG) {
1359 Slog.w(LOG_TAG, "Splicing from end:" + ops + " ratio:" + spliceRatio);
1360 }
1361 final HistoricalOps splice = ops.spliceFromEnd(spliceRatio);
1362 if (DEBUG) {
1363 Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
1364 }
1365 return splice;
1366 }
1367
1368
1369 private static @NonNull HistoricalOps spliceFromBeginning(@NonNull HistoricalOps ops,
1370 double spliceRatio) {
1371 if (DEBUG) {
1372 Slog.w(LOG_TAG, "Splicing from beginning:" + ops + " ratio:" + spliceRatio);
1373 }
1374 final HistoricalOps splice = ops.spliceFromBeginning(spliceRatio);
1375 if (DEBUG) {
1376 Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
1377 }
1378 return splice;
1379 }
1380
1381 private static void normalizeSnapshotForSlotDuration(@NonNull List<HistoricalOps> ops,
1382 long slotDurationMillis) {
1383 if (DEBUG) {
1384 Slog.i(LOG_TAG, "Normalizing for slot duration: " + slotDurationMillis
1385 + " ops:\n" + opsToDebugString(ops));
1386 enforceOpsWellFormed(ops);
1387 }
1388 long slotBeginTimeMillis;
1389 final int opCount = ops.size();
1390 for (int processedIdx = opCount - 1; processedIdx >= 0; processedIdx--) {
1391 final HistoricalOps processedOp = ops.get(processedIdx);
1392 slotBeginTimeMillis = Math.max(processedOp.getEndTimeMillis()
1393 - slotDurationMillis, 0);
1394 for (int candidateIdx = processedIdx - 1; candidateIdx >= 0; candidateIdx--) {
1395 final HistoricalOps candidateOp = ops.get(candidateIdx);
1396 final long candidateSlotIntersectionMillis = candidateOp.getEndTimeMillis()
1397 - Math.min(slotBeginTimeMillis, processedOp.getBeginTimeMillis());
1398 if (candidateSlotIntersectionMillis <= 0) {
1399 break;
1400 }
1401 final float candidateSplitRatio = candidateSlotIntersectionMillis
1402 / (float) candidateOp.getDurationMillis();
1403 if (Float.compare(candidateSplitRatio, 1.0f) >= 0) {
1404 ops.remove(candidateIdx);
1405 processedIdx--;
1406 processedOp.merge(candidateOp);
1407 } else {
1408 final HistoricalOps endSplice = spliceFromEnd(candidateOp,
1409 candidateSplitRatio);
1410 if (endSplice != null) {
1411 processedOp.merge(endSplice);
1412 }
1413 if (candidateOp.isEmpty()) {
1414 ops.remove(candidateIdx);
1415 processedIdx--;
1416 }
1417 }
1418 }
1419 }
1420 if (DEBUG) {
1421 Slog.i(LOG_TAG, "Normalized for slot duration: " + slotDurationMillis
1422 + " ops:\n" + opsToDebugString(ops));
1423 enforceOpsWellFormed(ops);
1424 }
1425 }
1426
1427 private static @NonNull String opsToDebugString(@NonNull List<HistoricalOps> ops) {
1428 StringBuilder builder = new StringBuilder();
1429 final int opCount = ops.size();
1430 for (int i = 0; i < opCount; i++) {
1431 builder.append(" ");
1432 builder.append(ops.get(i));
1433 if (i < opCount - 1) {
1434 builder.append('\n');
1435 }
1436 }
1437 return builder.toString();
1438 }
Svet Ganov80f500c2019-01-19 17:22:45 -08001439
1440 private static Set<String> getHistoricalFileNames(@NonNull File historyDir) {
1441 final File[] files = historyDir.listFiles();
1442 if (files == null) {
1443 return Collections.emptySet();
1444 }
1445 final ArraySet<String> fileNames = new ArraySet<>(files.length);
1446 for (File file : files) {
1447 fileNames.add(file.getName());
1448
1449 }
1450 return fileNames;
1451 }
1452 }
1453
1454 private static class HistoricalFilesInvariant {
1455 private final @NonNull List<File> mBeginFiles = new ArrayList<>();
1456
1457 public void startTracking(@NonNull File folder) {
1458 final File[] files = folder.listFiles();
1459 if (files != null) {
1460 Collections.addAll(mBeginFiles, files);
1461 }
1462 }
1463
1464 public void stopTracking(@NonNull File folder) {
1465 final List<File> endFiles = new ArrayList<>();
1466 final File[] files = folder.listFiles();
1467 if (files != null) {
1468 Collections.addAll(endFiles, files);
1469 }
1470 final long beginOldestFileOffsetMillis = getOldestFileOffsetMillis(mBeginFiles);
1471 final long endOldestFileOffsetMillis = getOldestFileOffsetMillis(endFiles);
1472 if (endOldestFileOffsetMillis < beginOldestFileOffsetMillis) {
1473 final String message = "History loss detected!"
1474 + "\nold files: " + mBeginFiles;
1475 wtf(message, null, folder);
1476 throw new IllegalStateException(message);
1477 }
1478 }
1479
1480 private static long getOldestFileOffsetMillis(@NonNull List<File> files) {
1481 if (files.isEmpty()) {
1482 return 0;
1483 }
1484 String longestName = files.get(0).getName();
1485 final int fileCount = files.size();
1486 for (int i = 1; i < fileCount; i++) {
1487 final File file = files.get(i);
1488 if (file.getName().length() > longestName.length()) {
1489 longestName = file.getName();
1490 }
1491 }
1492 return Long.parseLong(longestName.replace(HISTORY_FILE_SUFFIX, ""));
1493 }
Svet Ganov8455ba22019-01-02 13:05:56 -08001494 }
1495
1496 private final class StringDumpVisitor implements AppOpsManager.HistoricalOpsVisitor {
1497 private final long mNow = System.currentTimeMillis();
1498
1499 private final SimpleDateFormat mDateFormatter = new SimpleDateFormat(
1500 "yyyy-MM-dd HH:mm:ss.SSS");
1501 private final Date mDate = new Date();
1502
1503 private final @NonNull String mOpsPrefix;
1504 private final @NonNull String mUidPrefix;
1505 private final @NonNull String mPackagePrefix;
1506 private final @NonNull String mEntryPrefix;
1507 private final @NonNull String mUidStatePrefix;
1508 private final @NonNull PrintWriter mWriter;
1509 private final int mFilterUid;
1510 private final String mFilterPackage;
1511 private final int mFilterOp;
1512
1513 StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer,
1514 int filterUid, String filterPackage, int filterOp) {
1515 mOpsPrefix = prefix + " ";
1516 mUidPrefix = mOpsPrefix + " ";
1517 mPackagePrefix = mUidPrefix + " ";
1518 mEntryPrefix = mPackagePrefix + " ";
1519 mUidStatePrefix = mEntryPrefix + " ";
1520 mWriter = writer;
1521 mFilterUid = filterUid;
1522 mFilterPackage = filterPackage;
1523 mFilterOp = filterOp;
1524 }
1525
1526 @Override
1527 public void visitHistoricalOps(HistoricalOps ops) {
1528 mWriter.println();
1529 mWriter.print(mOpsPrefix);
1530 mWriter.println("snapshot:");
1531 mWriter.print(mUidPrefix);
1532 mWriter.print("begin = ");
1533 mDate.setTime(ops.getBeginTimeMillis());
1534 mWriter.print(mDateFormatter.format(mDate));
1535 mWriter.print(" (");
1536 TimeUtils.formatDuration(ops.getBeginTimeMillis() - mNow, mWriter);
1537 mWriter.println(")");
1538 mWriter.print(mUidPrefix);
1539 mWriter.print("end = ");
1540 mDate.setTime(ops.getEndTimeMillis());
1541 mWriter.print(mDateFormatter.format(mDate));
1542 mWriter.print(" (");
1543 TimeUtils.formatDuration(ops.getEndTimeMillis() - mNow, mWriter);
1544 mWriter.println(")");
1545 }
1546
1547 @Override
1548 public void visitHistoricalUidOps(HistoricalUidOps ops) {
1549 if (mFilterUid != Process.INVALID_UID && mFilterUid != ops.getUid()) {
1550 return;
1551 }
1552 mWriter.println();
1553 mWriter.print(mUidPrefix);
1554 mWriter.print("Uid ");
1555 UserHandle.formatUid(mWriter, ops.getUid());
1556 mWriter.println(":");
1557 }
1558
1559 @Override
1560 public void visitHistoricalPackageOps(HistoricalPackageOps ops) {
1561 if (mFilterPackage != null && !mFilterPackage.equals(ops.getPackageName())) {
1562 return;
1563 }
1564 mWriter.print(mPackagePrefix);
1565 mWriter.print("Package ");
1566 mWriter.print(ops.getPackageName());
1567 mWriter.println(":");
1568 }
1569
1570 @Override
1571 public void visitHistoricalOp(HistoricalOp ops) {
1572 if (mFilterOp != AppOpsManager.OP_NONE && mFilterOp != ops.getOpCode()) {
1573 return;
1574 }
1575 mWriter.print(mEntryPrefix);
1576 mWriter.print(AppOpsManager.opToName(ops.getOpCode()));
1577 mWriter.println(":");
Svet Ganovaf189e32019-02-15 18:45:29 -08001578 final LongSparseArray keys = ops.collectKeys();
1579 final int keyCount = keys.size();
1580 for (int i = 0; i < keyCount; i++) {
1581 final long key = keys.keyAt(i);
1582 final int uidState = AppOpsManager.extractUidStateFromKey(key);
1583 final int flags = AppOpsManager.extractFlagsFromKey(key);
Svet Ganov8455ba22019-01-02 13:05:56 -08001584 boolean printedUidState = false;
Svet Ganovaf189e32019-02-15 18:45:29 -08001585 final long accessCount = ops.getAccessCount(uidState, uidState, flags);
Svet Ganov8455ba22019-01-02 13:05:56 -08001586 if (accessCount > 0) {
1587 if (!printedUidState) {
1588 mWriter.print(mUidStatePrefix);
Svet Ganovaf189e32019-02-15 18:45:29 -08001589 mWriter.print(AppOpsManager.keyToString(key));
Joel Galenson775a8402019-01-14 15:23:15 -08001590 mWriter.print(" = ");
Svet Ganov8455ba22019-01-02 13:05:56 -08001591 printedUidState = true;
1592 }
1593 mWriter.print("access=");
1594 mWriter.print(accessCount);
1595 }
Svet Ganovaf189e32019-02-15 18:45:29 -08001596 final long rejectCount = ops.getRejectCount(uidState, uidState, flags);
Svet Ganov8455ba22019-01-02 13:05:56 -08001597 if (rejectCount > 0) {
1598 if (!printedUidState) {
1599 mWriter.print(mUidStatePrefix);
Svet Ganovaf189e32019-02-15 18:45:29 -08001600 mWriter.print(AppOpsManager.keyToString(key));
Joel Galenson775a8402019-01-14 15:23:15 -08001601 mWriter.print(" = ");
Svet Ganov8455ba22019-01-02 13:05:56 -08001602 printedUidState = true;
1603 } else {
Joel Galenson775a8402019-01-14 15:23:15 -08001604 mWriter.print(", ");
Svet Ganov8455ba22019-01-02 13:05:56 -08001605 }
1606 mWriter.print("reject=");
1607 mWriter.print(rejectCount);
1608 }
Svet Ganovaf189e32019-02-15 18:45:29 -08001609 final long accessDuration = ops.getAccessDuration(uidState, uidState, flags);
Svet Ganov8455ba22019-01-02 13:05:56 -08001610 if (accessDuration > 0) {
1611 if (!printedUidState) {
1612 mWriter.print(mUidStatePrefix);
Svet Ganovaf189e32019-02-15 18:45:29 -08001613 mWriter.print(AppOpsManager.keyToString(key));
Joel Galenson775a8402019-01-14 15:23:15 -08001614 mWriter.print(" = ");
Svet Ganov8455ba22019-01-02 13:05:56 -08001615 printedUidState = true;
1616 } else {
Joel Galenson775a8402019-01-14 15:23:15 -08001617 mWriter.print(", ");
Svet Ganov8455ba22019-01-02 13:05:56 -08001618 }
1619 mWriter.print("duration=");
Joel Galenson775a8402019-01-14 15:23:15 -08001620 TimeUtils.formatDuration(accessDuration, mWriter);
Svet Ganov8455ba22019-01-02 13:05:56 -08001621 }
1622 if (printedUidState) {
Joel Galenson775a8402019-01-14 15:23:15 -08001623 mWriter.println("");
Svet Ganov8455ba22019-01-02 13:05:56 -08001624 }
1625 }
1626 }
1627 }
Svet Ganov80f500c2019-01-19 17:22:45 -08001628
1629 private static void wtf(@Nullable String message, @Nullable Throwable t,
1630 @Nullable File storage) {
1631 Slog.wtf(LOG_TAG, message, t);
1632 if (KEEP_WTF_LOG) {
1633 try {
1634 final File file = new File(new File(Environment.getDataSystemDirectory(), "appops"),
1635 "wtf" + TimeUtils.formatForLogging(System.currentTimeMillis()));
1636 if (file.createNewFile()) {
1637 try (PrintWriter writer = new PrintWriter(file)) {
1638 if (t != null) {
1639 writer.append('\n').append(t.toString());
1640 }
1641 writer.append('\n').append(Debug.getCallers(10));
1642 if (storage != null) {
1643 writer.append("\nfiles: " + Arrays.toString(storage.listFiles()));
1644 } else {
1645 writer.append("\nfiles: none");
1646 }
1647 }
1648 }
1649 } catch (IOException e) {
1650 /* ignore */
1651 }
1652 }
1653 }
Svet Ganov8455ba22019-01-02 13:05:56 -08001654}