blob: ed4506902f0ab675bcf7db30690633ac6c748cf4 [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
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -080018import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
Philip P. Moltmann4aacd712020-01-03 12:32:20 -080019import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
20import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
21import static android.app.AppOpsManager.FILTER_BY_UID;
22
Svet Ganov8455ba22019-01-02 13:05:56 -080023import android.annotation.NonNull;
24import android.annotation.Nullable;
25import android.app.AppOpsManager;
26import android.app.AppOpsManager.HistoricalMode;
27import android.app.AppOpsManager.HistoricalOp;
28import android.app.AppOpsManager.HistoricalOps;
Philip P. Moltmann4aacd712020-01-03 12:32:20 -080029import android.app.AppOpsManager.HistoricalOpsRequestFilter;
Svet Ganov8455ba22019-01-02 13:05:56 -080030import android.app.AppOpsManager.HistoricalPackageOps;
31import android.app.AppOpsManager.HistoricalUidOps;
Svet Ganovaf189e32019-02-15 18:45:29 -080032import android.app.AppOpsManager.OpFlags;
Svet Ganov8455ba22019-01-02 13:05:56 -080033import android.app.AppOpsManager.UidState;
34import android.content.ContentResolver;
35import android.database.ContentObserver;
36import android.net.Uri;
Hai Zhang00c05eb2019-09-06 16:47:42 -070037import android.os.Binder;
Svet Ganov80f500c2019-01-19 17:22:45 -080038import android.os.Build;
Svet Ganov8455ba22019-01-02 13:05:56 -080039import android.os.Bundle;
Svet Ganov80f500c2019-01-19 17:22:45 -080040import android.os.Debug;
Svet Ganov8455ba22019-01-02 13:05:56 -080041import android.os.Environment;
42import android.os.Message;
43import android.os.Process;
44import android.os.RemoteCallback;
45import android.os.UserHandle;
Hai Zhang00c05eb2019-09-06 16:47:42 -070046import android.provider.DeviceConfig;
Svet Ganov8455ba22019-01-02 13:05:56 -080047import android.provider.Settings;
48import android.util.ArraySet;
Svet Ganovaf189e32019-02-15 18:45:29 -080049import android.util.LongSparseArray;
Svet Ganov8455ba22019-01-02 13:05:56 -080050import android.util.Slog;
51import android.util.TimeUtils;
52import android.util.Xml;
Svet Ganov80f500c2019-01-19 17:22:45 -080053
Svet Ganov8455ba22019-01-02 13:05:56 -080054import com.android.internal.annotations.GuardedBy;
55import com.android.internal.os.AtomicDirectory;
56import com.android.internal.os.BackgroundThread;
57import com.android.internal.util.ArrayUtils;
58import com.android.internal.util.XmlUtils;
59import com.android.internal.util.function.pooled.PooledLambda;
60import com.android.server.FgThread;
Svet Ganov80f500c2019-01-19 17:22:45 -080061
Svet Ganov8455ba22019-01-02 13:05:56 -080062import org.xmlpull.v1.XmlPullParser;
63import org.xmlpull.v1.XmlPullParserException;
64import org.xmlpull.v1.XmlSerializer;
65
66import java.io.File;
67import java.io.FileInputStream;
68import java.io.FileNotFoundException;
69import java.io.FileOutputStream;
70import java.io.IOException;
71import java.io.PrintWriter;
72import java.nio.charset.StandardCharsets;
73import java.nio.file.Files;
74import java.text.SimpleDateFormat;
75import java.util.ArrayList;
Svet Ganov80f500c2019-01-19 17:22:45 -080076import java.util.Arrays;
Svet Ganov8455ba22019-01-02 13:05:56 -080077import java.util.Collections;
78import java.util.Date;
79import java.util.LinkedList;
80import java.util.List;
Philip P. Moltmann4aacd712020-01-03 12:32:20 -080081import java.util.Objects;
Svet Ganov80f500c2019-01-19 17:22:45 -080082import java.util.Set;
Svet Ganov8455ba22019-01-02 13:05:56 -080083import java.util.concurrent.TimeUnit;
84
85/**
86 * This class managers historical app op state. This includes reading, persistence,
87 * accounting, querying.
88 * <p>
89 * The history is kept forever in multiple files. Each file time contains the
90 * relative offset from the current time which time is encoded in the file name.
91 * The files contain historical app op state snapshots which have times that
92 * are relative to the time of the container file.
93 *
94 * The data in the files are stored in a logarithmic fashion where where every
95 * subsequent file would contain data for ten times longer interval with ten
96 * times more time distance between snapshots. Hence, the more time passes
97 * the lesser the fidelity.
98 * <p>
99 * For example, the first file would contain data for 1 days with snapshots
100 * every 0.1 days, the next file would contain data for the period 1 to 10
101 * days with snapshots every 1 days, and so on.
102 * <p>
103 * THREADING AND LOCKING: Reported ops must be processed as quickly as possible.
104 * We keep ops pending to be persisted in memory and write to disk on a background
105 * thread. Hence, methods that report op changes are locking only the in memory
106 * state guarded by the mInMemoryLock which happens to be the app ops service lock
107 * avoiding a lock addition on the critical path. When a query comes we need to
108 * evaluate it based off both in memory and on disk state. This means they need to
109 * be frozen with respect to each other and not change from the querying caller's
110 * perspective. To achieve this we add a dedicated mOnDiskLock to guard the on
111 * disk state. To have fast critical path we need to limit the locking of the
112 * mInMemoryLock, thus for operations that touch in memory and on disk state one
113 * must grab first the mOnDiskLock and then the mInMemoryLock and limit the
114 * in memory lock to extraction of relevant data. Locking order is critical to
115 * avoid deadlocks. The convention is that xxxDLocked suffix means the method
116 * must be called with the mOnDiskLock lock, xxxMLocked suffix means the method
117 * must be called with the mInMemoryLock, xxxDMLocked suffix means the method
118 * must be called with the mOnDiskLock and mInMemoryLock locks acquired in that
119 * exact order.
Svet Ganov47641072019-06-06 12:57:12 -0700120 * <p>
121 * INITIALIZATION: We can initialize persistence only after the system is ready
122 * as we need to check the optional configuration override from the settings
123 * database which is not initialized at the time the app ops service is created.
124 * This means that all entry points that touch persistence should be short
125 * circuited via isPersistenceInitialized() check.
Svet Ganov8455ba22019-01-02 13:05:56 -0800126 */
127// TODO (bug:122218838): Make sure we handle start of epoch time
128// TODO (bug:122218838): Validate changed time is handled correctly
129final class HistoricalRegistry {
130 private static final boolean DEBUG = false;
Svet Ganov80f500c2019-01-19 17:22:45 -0800131 private static final boolean KEEP_WTF_LOG = Build.IS_DEBUGGABLE;
Svet Ganov8455ba22019-01-02 13:05:56 -0800132
133 private static final String LOG_TAG = HistoricalRegistry.class.getSimpleName();
134
135 private static final String PARAMETER_DELIMITER = ",";
136 private static final String PARAMETER_ASSIGNMENT = "=";
Fabian Kozynski228e3102019-11-25 11:04:34 -0500137 private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
Svet Ganov8455ba22019-01-02 13:05:56 -0800138
139 @GuardedBy("mLock")
140 private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
141
142 // Lock for read/write access to on disk state
143 private final Object mOnDiskLock = new Object();
144
145 //Lock for read/write access to in memory state
146 private final @NonNull Object mInMemoryLock;
147
148 private static final int MSG_WRITE_PENDING_HISTORY = 1;
149
150 // See mMode
Svet Ganov80f500c2019-01-19 17:22:45 -0800151 private static final int DEFAULT_MODE = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
Svet Ganov8455ba22019-01-02 13:05:56 -0800152
153 // See mBaseSnapshotInterval
154 private static final long DEFAULT_SNAPSHOT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(15);
155
156 // See mIntervalCompressionMultiplier
157 private static final long DEFAULT_COMPRESSION_STEP = 10;
158
Svet Ganov80f500c2019-01-19 17:22:45 -0800159 private static final String HISTORY_FILE_SUFFIX = ".xml";
160
Svet Ganov8455ba22019-01-02 13:05:56 -0800161 /**
162 * Whether history is enabled.
163 */
164 @GuardedBy("mInMemoryLock")
Philip P. Moltmann0027a672019-06-29 03:36:19 +0000165 private int mMode = AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE;
Svet Ganov8455ba22019-01-02 13:05:56 -0800166
167 /**
168 * This granularity has been chosen to allow clean delineation for intervals
169 * humans understand, 15 min, 60, min, a day, a week, a month (30 days).
170 */
171 @GuardedBy("mInMemoryLock")
172 private long mBaseSnapshotInterval = DEFAULT_SNAPSHOT_INTERVAL_MILLIS;
173
174 /**
175 * The compression between steps. Each subsequent step is this much longer
176 * in terms of duration and each snapshot is this much more apart from the
177 * previous step.
178 */
179 @GuardedBy("mInMemoryLock")
180 private long mIntervalCompressionMultiplier = DEFAULT_COMPRESSION_STEP;
181
182 // The current ops to which to add statistics.
183 @GuardedBy("mInMemoryLock")
184 private @Nullable HistoricalOps mCurrentHistoricalOps;
185
186 // The time we should write the next snapshot.
187 @GuardedBy("mInMemoryLock")
188 private long mNextPersistDueTimeMillis;
189
190 // How much to offset the history on the next write.
191 @GuardedBy("mInMemoryLock")
192 private long mPendingHistoryOffsetMillis;
193
194 // Object managing persistence (read/write)
195 @GuardedBy("mOnDiskLock")
Svet Ganov47641072019-06-06 12:57:12 -0700196 private Persistence mPersistence;
Svet Ganov8455ba22019-01-02 13:05:56 -0800197
198 HistoricalRegistry(@NonNull Object lock) {
199 mInMemoryLock = lock;
Svet Ganov47641072019-06-06 12:57:12 -0700200 }
201
202 void systemReady(@NonNull ContentResolver resolver) {
203 final Uri uri = Settings.Global.getUriFor(Settings.Global.APPOP_HISTORY_PARAMETERS);
204 resolver.registerContentObserver(uri, false, new ContentObserver(
205 FgThread.getHandler()) {
206 @Override
207 public void onChange(boolean selfChange) {
208 updateParametersFromSetting(resolver);
209 }
210 });
211
212 updateParametersFromSetting(resolver);
213
214 synchronized (mOnDiskLock) {
215 synchronized (mInMemoryLock) {
216 if (mMode != AppOpsManager.HISTORICAL_MODE_DISABLED) {
217 // Can be uninitialized if there is no config in the settings table.
218 if (!isPersistenceInitializedMLocked()) {
219 mPersistence = new Persistence(mBaseSnapshotInterval,
220 mIntervalCompressionMultiplier);
221 }
222
Svet Ganov80f500c2019-01-19 17:22:45 -0800223 // When starting always adjust history to now.
224 final long lastPersistTimeMills =
225 mPersistence.getLastPersistTimeMillisDLocked();
226 if (lastPersistTimeMills > 0) {
227 mPendingHistoryOffsetMillis =
228 System.currentTimeMillis() - lastPersistTimeMills;
229 }
230 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800231 }
232 }
233 }
234
Svet Ganov47641072019-06-06 12:57:12 -0700235 private boolean isPersistenceInitializedMLocked() {
236 return mPersistence != null;
Svet Ganov8455ba22019-01-02 13:05:56 -0800237 }
238
239 private void updateParametersFromSetting(@NonNull ContentResolver resolver) {
240 final String setting = Settings.Global.getString(resolver,
241 Settings.Global.APPOP_HISTORY_PARAMETERS);
242 if (setting == null) {
243 return;
244 }
245 String modeValue = null;
246 String baseSnapshotIntervalValue = null;
247 String intervalMultiplierValue = null;
248 final String[] parameters = setting.split(PARAMETER_DELIMITER);
249 for (String parameter : parameters) {
250 final String[] parts = parameter.split(PARAMETER_ASSIGNMENT);
251 if (parts.length == 2) {
252 final String key = parts[0].trim();
253 switch (key) {
254 case Settings.Global.APPOP_HISTORY_MODE: {
255 modeValue = parts[1].trim();
256 } break;
257 case Settings.Global.APPOP_HISTORY_BASE_INTERVAL_MILLIS: {
258 baseSnapshotIntervalValue = parts[1].trim();
259 } break;
260 case Settings.Global.APPOP_HISTORY_INTERVAL_MULTIPLIER: {
261 intervalMultiplierValue = parts[1].trim();
262 } break;
263 default: {
264 Slog.w(LOG_TAG, "Unknown parameter: " + parameter);
265 }
266 }
267 }
268 }
269 if (modeValue != null && baseSnapshotIntervalValue != null
270 && intervalMultiplierValue != null) {
271 try {
272 final int mode = AppOpsManager.parseHistoricalMode(modeValue);
273 final long baseSnapshotInterval = Long.parseLong(baseSnapshotIntervalValue);
274 final int intervalCompressionMultiplier = Integer.parseInt(intervalMultiplierValue);
275 setHistoryParameters(mode, baseSnapshotInterval,intervalCompressionMultiplier);
276 return;
277 } catch (NumberFormatException ignored) {}
278 }
279 Slog.w(LOG_TAG, "Bad value for" + Settings.Global.APPOP_HISTORY_PARAMETERS
280 + "=" + setting + " resetting!");
281 }
282
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800283 void dump(String prefix, PrintWriter pw, int filterUid, @Nullable String filterPackage,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800284 @Nullable String filterAttributionTag, int filterOp,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800285 @HistoricalOpsRequestFilter int filter) {
Hai Zhang00c05eb2019-09-06 16:47:42 -0700286 if (!isApiEnabled()) {
287 return;
288 }
289
Svet Ganov8455ba22019-01-02 13:05:56 -0800290 synchronized (mOnDiskLock) {
291 synchronized (mInMemoryLock) {
292 pw.println();
293 pw.print(prefix);
294 pw.print("History:");
295
296 pw.print(" mode=");
297 pw.println(AppOpsManager.historicalModeToString(mMode));
298
299 final StringDumpVisitor visitor = new StringDumpVisitor(prefix + " ",
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800300 pw, filterUid, filterPackage, filterAttributionTag, filterOp, filter);
Svet Ganov8455ba22019-01-02 13:05:56 -0800301 final long nowMillis = System.currentTimeMillis();
302
303 // Dump in memory state first
304 final HistoricalOps currentOps = getUpdatedPendingHistoricalOpsMLocked(
305 nowMillis);
306 makeRelativeToEpochStart(currentOps, nowMillis);
307 currentOps.accept(visitor);
308
Hai Zhangf5387672019-08-13 15:55:58 -0700309 if (!isPersistenceInitializedMLocked()) {
Svet Ganov47641072019-06-06 12:57:12 -0700310 Slog.e(LOG_TAG, "Interaction before persistence initialized");
311 return;
312 }
313
Svet Ganov8455ba22019-01-02 13:05:56 -0800314 final List<HistoricalOps> ops = mPersistence.readHistoryDLocked();
315 if (ops != null) {
316 // TODO (bug:122218838): Make sure this is properly dumped
317 final long remainingToFillBatchMillis = mNextPersistDueTimeMillis
318 - nowMillis - mBaseSnapshotInterval;
319 final int opCount = ops.size();
320 for (int i = 0; i < opCount; i++) {
321 final HistoricalOps op = ops.get(i);
322 op.offsetBeginAndEndTime(remainingToFillBatchMillis);
323 makeRelativeToEpochStart(op, nowMillis);
324 op.accept(visitor);
325 }
326 } else {
327 pw.println(" Empty");
328 }
329 }
330 }
331 }
332
333 @HistoricalMode int getMode() {
334 synchronized (mInMemoryLock) {
335 return mMode;
336 }
337 }
338
Svet Ganovaf189e32019-02-15 18:45:29 -0800339 void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800340 @Nullable String attributionTag, @Nullable String[] opNames,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800341 @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -0800342 @OpFlags int flags, @NonNull RemoteCallback callback) {
Hai Zhang00c05eb2019-09-06 16:47:42 -0700343 if (!isApiEnabled()) {
344 callback.sendResult(new Bundle());
345 return;
346 }
347
Svet Ganov47641072019-06-06 12:57:12 -0700348 synchronized (mOnDiskLock) {
349 synchronized (mInMemoryLock) {
350 if (!isPersistenceInitializedMLocked()) {
351 Slog.e(LOG_TAG, "Interaction before persistence initialized");
352 callback.sendResult(new Bundle());
353 return;
354 }
355 final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800356 mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, attributionTag,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800357 opNames, filter, beginTimeMillis, endTimeMillis, flags);
Svet Ganov47641072019-06-06 12:57:12 -0700358 final Bundle payload = new Bundle();
359 payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
360 callback.sendResult(payload);
361 }
362 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800363 }
364
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800365 void getHistoricalOps(int uid, @NonNull String packageName, @Nullable String attributionTag,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800366 @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
367 long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
368 @NonNull RemoteCallback callback) {
Hai Zhang00c05eb2019-09-06 16:47:42 -0700369 if (!isApiEnabled()) {
370 callback.sendResult(new Bundle());
371 return;
372 }
373
Svet Ganov8455ba22019-01-02 13:05:56 -0800374 final long currentTimeMillis = System.currentTimeMillis();
375 if (endTimeMillis == Long.MAX_VALUE) {
376 endTimeMillis = currentTimeMillis;
377 }
378
379 // Argument times are based off epoch start while our internal store is
380 // based off now, so take this into account.
381 final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0);
382 final long inMemoryAdjEndTimeMillis = Math.max(currentTimeMillis - beginTimeMillis, 0);
383 final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis,
384 inMemoryAdjEndTimeMillis);
385
386 synchronized (mOnDiskLock) {
387 final List<HistoricalOps> pendingWrites;
388 final HistoricalOps currentOps;
Svet Ganovaf189e32019-02-15 18:45:29 -0800389 boolean collectOpsFromDisk;
390
Svet Ganov8455ba22019-01-02 13:05:56 -0800391 synchronized (mInMemoryLock) {
Svet Ganov47641072019-06-06 12:57:12 -0700392 if (!isPersistenceInitializedMLocked()) {
393 Slog.e(LOG_TAG, "Interaction before persistence initialized");
394 callback.sendResult(new Bundle());
395 return;
396 }
397
Svet Ganov8455ba22019-01-02 13:05:56 -0800398 currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis);
399 if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis()
400 || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
401 // Some of the current batch falls into the query, so extract that.
402 final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800403 currentOpsCopy.filter(uid, packageName, attributionTag, opNames, filter,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800404 inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis);
Svet Ganov8455ba22019-01-02 13:05:56 -0800405 result.merge(currentOpsCopy);
406 }
407 pendingWrites = new ArrayList<>(mPendingWrites);
408 mPendingWrites.clear();
Svet Ganovaf189e32019-02-15 18:45:29 -0800409 collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis();
Svet Ganov8455ba22019-01-02 13:05:56 -0800410 }
411
412 // If the query was only for in-memory state - done.
Svet Ganovaf189e32019-02-15 18:45:29 -0800413 if (collectOpsFromDisk) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800414 // If there is a write in flight we need to force it now
415 persistPendingHistory(pendingWrites);
416 // Collect persisted state.
417 final long onDiskAndInMemoryOffsetMillis = currentTimeMillis
418 - mNextPersistDueTimeMillis + mBaseSnapshotInterval;
419 final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis
420 - onDiskAndInMemoryOffsetMillis, 0);
421 final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
422 - onDiskAndInMemoryOffsetMillis, 0);
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800423 mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, attributionTag,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800424 opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags);
Svet Ganov8455ba22019-01-02 13:05:56 -0800425 }
426
427 // Rebase the result time to be since epoch.
428 result.setBeginAndEndTime(beginTimeMillis, endTimeMillis);
429
430 // Send back the result.
431 final Bundle payload = new Bundle();
432 payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
433 callback.sendResult(payload);
434 }
435 }
436
437 void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800438 @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800439 synchronized (mInMemoryLock) {
440 if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
Svet Ganov47641072019-06-06 12:57:12 -0700441 if (!isPersistenceInitializedMLocked()) {
442 Slog.e(LOG_TAG, "Interaction before persistence initialized");
443 return;
444 }
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800445 getUpdatedPendingHistoricalOpsMLocked(
446 System.currentTimeMillis()).increaseAccessCount(op, uid, packageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800447 attributionTag, uidState, flags, 1);
Svet Ganov8455ba22019-01-02 13:05:56 -0800448 }
449 }
450 }
451
452 void incrementOpRejected(int op, int uid, @NonNull String packageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800453 @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800454 synchronized (mInMemoryLock) {
455 if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
Svet Ganov47641072019-06-06 12:57:12 -0700456 if (!isPersistenceInitializedMLocked()) {
457 Slog.e(LOG_TAG, "Interaction before persistence initialized");
458 return;
459 }
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800460 getUpdatedPendingHistoricalOpsMLocked(
461 System.currentTimeMillis()).increaseRejectCount(op, uid, packageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800462 attributionTag, uidState, flags, 1);
Svet Ganov8455ba22019-01-02 13:05:56 -0800463 }
464 }
465 }
466
467 void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800468 @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800469 long increment) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800470 synchronized (mInMemoryLock) {
471 if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
Svet Ganov47641072019-06-06 12:57:12 -0700472 if (!isPersistenceInitializedMLocked()) {
473 Slog.e(LOG_TAG, "Interaction before persistence initialized");
474 return;
475 }
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800476 getUpdatedPendingHistoricalOpsMLocked(
477 System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800478 attributionTag, uidState, flags, increment);
Svet Ganov8455ba22019-01-02 13:05:56 -0800479 }
480 }
481 }
482
483 void setHistoryParameters(@HistoricalMode int mode,
484 long baseSnapshotInterval, long intervalCompressionMultiplier) {
485 synchronized (mOnDiskLock) {
486 synchronized (mInMemoryLock) {
Svet Ganov47641072019-06-06 12:57:12 -0700487 // NOTE: We allow this call if persistence is not initialized as
488 // it is a part of the persistence initialization process.
Svet Ganov8455ba22019-01-02 13:05:56 -0800489 boolean resampleHistory = false;
490 Slog.i(LOG_TAG, "New history parameters: mode:"
Hai Zhangf5387672019-08-13 15:55:58 -0700491 + AppOpsManager.historicalModeToString(mode) + " baseSnapshotInterval:"
Svet Ganov8455ba22019-01-02 13:05:56 -0800492 + baseSnapshotInterval + " intervalCompressionMultiplier:"
493 + intervalCompressionMultiplier);
494 if (mMode != mode) {
495 mMode = mode;
496 if (mMode == AppOpsManager.HISTORICAL_MODE_DISABLED) {
Svet Ganov47641072019-06-06 12:57:12 -0700497 clearHistoryOnDiskDLocked();
Svet Ganov8455ba22019-01-02 13:05:56 -0800498 }
499 }
500 if (mBaseSnapshotInterval != baseSnapshotInterval) {
501 mBaseSnapshotInterval = baseSnapshotInterval;
502 resampleHistory = true;
503 }
504 if (mIntervalCompressionMultiplier != intervalCompressionMultiplier) {
505 mIntervalCompressionMultiplier = intervalCompressionMultiplier;
506 resampleHistory = true;
507 }
508 if (resampleHistory) {
509 resampleHistoryOnDiskInMemoryDMLocked(0);
510 }
511 }
512 }
513 }
514
515 void offsetHistory(long offsetMillis) {
516 synchronized (mOnDiskLock) {
517 synchronized (mInMemoryLock) {
Svet Ganov47641072019-06-06 12:57:12 -0700518 if (!isPersistenceInitializedMLocked()) {
519 Slog.e(LOG_TAG, "Interaction before persistence initialized");
520 return;
521 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800522 final List<HistoricalOps> history = mPersistence.readHistoryDLocked();
523 clearHistory();
524 if (history != null) {
525 final int historySize = history.size();
526 for (int i = 0; i < historySize; i++) {
527 final HistoricalOps ops = history.get(i);
528 ops.offsetBeginAndEndTime(offsetMillis);
529 }
530 if (offsetMillis < 0) {
531 pruneFutureOps(history);
532 }
533 mPersistence.persistHistoricalOpsDLocked(history);
534 }
535 }
536 }
537 }
538
539 void addHistoricalOps(HistoricalOps ops) {
540 final List<HistoricalOps> pendingWrites;
541 synchronized (mInMemoryLock) {
Svet Ganov47641072019-06-06 12:57:12 -0700542 if (!isPersistenceInitializedMLocked()) {
543 Slog.e(LOG_TAG, "Interaction before persistence initialized");
544 return;
545 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800546 // The history files start from mBaseSnapshotInterval - take this into account.
547 ops.offsetBeginAndEndTime(mBaseSnapshotInterval);
548 mPendingWrites.offerFirst(ops);
549 pendingWrites = new ArrayList<>(mPendingWrites);
550 mPendingWrites.clear();
551 }
552 persistPendingHistory(pendingWrites);
553 }
554
555 private void resampleHistoryOnDiskInMemoryDMLocked(long offsetMillis) {
556 mPersistence = new Persistence(mBaseSnapshotInterval, mIntervalCompressionMultiplier);
557 offsetHistory(offsetMillis);
558 }
559
560 void resetHistoryParameters() {
Svet Ganov47641072019-06-06 12:57:12 -0700561 if (!isPersistenceInitializedMLocked()) {
562 Slog.e(LOG_TAG, "Interaction before persistence initialized");
563 return;
564 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800565 setHistoryParameters(DEFAULT_MODE, DEFAULT_SNAPSHOT_INTERVAL_MILLIS,
566 DEFAULT_COMPRESSION_STEP);
567 }
568
Winson470b15b2019-05-07 16:29:59 -0700569 void clearHistory(int uid, String packageName) {
570 synchronized (mOnDiskLock) {
571 synchronized (mInMemoryLock) {
Svet Ganov47641072019-06-06 12:57:12 -0700572 if (!isPersistenceInitializedMLocked()) {
573 Slog.e(LOG_TAG, "Interaction before persistence initialized");
574 return;
575 }
Winson470b15b2019-05-07 16:29:59 -0700576 if (mMode != AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
577 return;
578 }
579
580 for (int index = 0; index < mPendingWrites.size(); index++) {
581 mPendingWrites.get(index).clearHistory(uid, packageName);
582 }
583
584 getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
585 .clearHistory(uid, packageName);
586
587 mPersistence.clearHistoryDLocked(uid, packageName);
588 }
589 }
590 }
591
Svet Ganov8455ba22019-01-02 13:05:56 -0800592 void clearHistory() {
593 synchronized (mOnDiskLock) {
Svet Ganov47641072019-06-06 12:57:12 -0700594 synchronized (mInMemoryLock) {
595 if (!isPersistenceInitializedMLocked()) {
596 Slog.e(LOG_TAG, "Interaction before persistence initialized");
597 return;
598 }
599 clearHistoryOnDiskDLocked();
600 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800601 }
602 }
603
Svet Ganov47641072019-06-06 12:57:12 -0700604 private void clearHistoryOnDiskDLocked() {
Svet Ganov8455ba22019-01-02 13:05:56 -0800605 BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
606 synchronized (mInMemoryLock) {
607 mCurrentHistoricalOps = null;
608 mNextPersistDueTimeMillis = System.currentTimeMillis();
609 mPendingWrites.clear();
610 }
Svet Ganov47641072019-06-06 12:57:12 -0700611 Persistence.clearHistoryDLocked();
Svet Ganov8455ba22019-01-02 13:05:56 -0800612 }
613
614 private @NonNull HistoricalOps getUpdatedPendingHistoricalOpsMLocked(long now) {
615 if (mCurrentHistoricalOps != null) {
616 final long remainingTimeMillis = mNextPersistDueTimeMillis - now;
617 if (remainingTimeMillis > mBaseSnapshotInterval) {
618 // If time went backwards we need to push history to the future with the
619 // overflow over our snapshot interval. If time went forward do nothing
620 // as we would naturally push history into the past on the next write.
621 mPendingHistoryOffsetMillis = remainingTimeMillis - mBaseSnapshotInterval;
622 }
623 final long elapsedTimeMillis = mBaseSnapshotInterval - remainingTimeMillis;
624 mCurrentHistoricalOps.setEndTime(elapsedTimeMillis);
625 if (remainingTimeMillis > 0) {
626 if (DEBUG) {
627 Slog.i(LOG_TAG, "Returning current in-memory state");
628 }
629 return mCurrentHistoricalOps;
630 }
631 if (mCurrentHistoricalOps.isEmpty()) {
632 mCurrentHistoricalOps.setBeginAndEndTime(0, 0);
633 mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
634 return mCurrentHistoricalOps;
635 }
636 // The current batch is full, so persist taking into account overdue persist time.
637 mCurrentHistoricalOps.offsetBeginAndEndTime(mBaseSnapshotInterval);
638 mCurrentHistoricalOps.setBeginTime(mCurrentHistoricalOps.getEndTimeMillis()
639 - mBaseSnapshotInterval);
640 final long overdueTimeMillis = Math.abs(remainingTimeMillis);
641 mCurrentHistoricalOps.offsetBeginAndEndTime(overdueTimeMillis);
642 schedulePersistHistoricalOpsMLocked(mCurrentHistoricalOps);
643 }
644 // The current batch is in the future, i.e. not complete yet.
645 mCurrentHistoricalOps = new HistoricalOps(0, 0);
646 mNextPersistDueTimeMillis = now + mBaseSnapshotInterval;
647 if (DEBUG) {
648 Slog.i(LOG_TAG, "Returning new in-memory state");
649 }
650 return mCurrentHistoricalOps;
651 }
652
653 private void persistPendingHistory() {
654 final List<HistoricalOps> pendingWrites;
655 synchronized (mOnDiskLock) {
656 synchronized (mInMemoryLock) {
657 pendingWrites = new ArrayList<>(mPendingWrites);
658 mPendingWrites.clear();
659 if (mPendingHistoryOffsetMillis != 0) {
660 resampleHistoryOnDiskInMemoryDMLocked(mPendingHistoryOffsetMillis);
661 mPendingHistoryOffsetMillis = 0;
662 }
663 }
664 persistPendingHistory(pendingWrites);
665 }
666 }
Svet Ganov80f500c2019-01-19 17:22:45 -0800667
Svet Ganov8455ba22019-01-02 13:05:56 -0800668 private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
669 synchronized (mOnDiskLock) {
670 BackgroundThread.getHandler().removeMessages(MSG_WRITE_PENDING_HISTORY);
671 if (pendingWrites.isEmpty()) {
672 return;
673 }
674 final int opCount = pendingWrites.size();
675 // Pending writes are offset relative to each other, so take this
676 // into account to persist everything in one shot - single write.
677 for (int i = 0; i < opCount; i++) {
678 final HistoricalOps current = pendingWrites.get(i);
679 if (i > 0) {
680 final HistoricalOps previous = pendingWrites.get(i - 1);
681 current.offsetBeginAndEndTime(previous.getBeginTimeMillis());
682 }
683 }
684 mPersistence.persistHistoricalOpsDLocked(pendingWrites);
685 }
686 }
687
688 private void schedulePersistHistoricalOpsMLocked(@NonNull HistoricalOps ops) {
689 final Message message = PooledLambda.obtainMessage(
690 HistoricalRegistry::persistPendingHistory, HistoricalRegistry.this);
691 message.what = MSG_WRITE_PENDING_HISTORY;
692 BackgroundThread.getHandler().sendMessage(message);
693 mPendingWrites.offerFirst(ops);
694 }
695
696 private static void makeRelativeToEpochStart(@NonNull HistoricalOps ops, long nowMillis) {
697 ops.setBeginAndEndTime(nowMillis - ops.getEndTimeMillis(),
698 nowMillis- ops.getBeginTimeMillis());
699 }
700
701 private void pruneFutureOps(@NonNull List<HistoricalOps> ops) {
702 final int opCount = ops.size();
703 for (int i = opCount - 1; i >= 0; i--) {
704 final HistoricalOps op = ops.get(i);
705 if (op.getEndTimeMillis() <= mBaseSnapshotInterval) {
706 ops.remove(i);
707 } else if (op.getBeginTimeMillis() < mBaseSnapshotInterval) {
708 final double filterScale = (double) (op.getEndTimeMillis() - mBaseSnapshotInterval)
709 / (double) op.getDurationMillis();
710 Persistence.spliceFromBeginning(op, filterScale);
711 }
712 }
713 }
714
Hai Zhang00c05eb2019-09-06 16:47:42 -0700715 private static boolean isApiEnabled() {
716 return Binder.getCallingUid() == Process.myUid()
717 || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
Fabian Kozynski228e3102019-11-25 11:04:34 -0500718 PROPERTY_PERMISSIONS_HUB_ENABLED, false);
Hai Zhang00c05eb2019-09-06 16:47:42 -0700719 }
720
Svet Ganov8455ba22019-01-02 13:05:56 -0800721 private static final class Persistence {
722 private static final boolean DEBUG = false;
723
724 private static final String LOG_TAG = Persistence.class.getSimpleName();
725
Svet Ganov8455ba22019-01-02 13:05:56 -0800726 private static final String TAG_HISTORY = "history";
727 private static final String TAG_OPS = "ops";
728 private static final String TAG_UID = "uid";
Svet Ganovaf189e32019-02-15 18:45:29 -0800729 private static final String TAG_PACKAGE = "pkg";
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800730 private static final String TAG_ATTRIBUTION = "ftr";
Svet Ganov8455ba22019-01-02 13:05:56 -0800731 private static final String TAG_OP = "op";
Svet Ganovaf189e32019-02-15 18:45:29 -0800732 private static final String TAG_STATE = "st";
Svet Ganov8455ba22019-01-02 13:05:56 -0800733
Svet Ganovaf189e32019-02-15 18:45:29 -0800734 private static final String ATTR_VERSION = "ver";
735 private static final String ATTR_NAME = "na";
736 private static final String ATTR_ACCESS_COUNT = "ac";
737 private static final String ATTR_REJECT_COUNT = "rc";
738 private static final String ATTR_ACCESS_DURATION = "du";
739 private static final String ATTR_BEGIN_TIME = "beg";
740 private static final String ATTR_END_TIME = "end";
741 private static final String ATTR_OVERFLOW = "ov";
Svet Ganov8455ba22019-01-02 13:05:56 -0800742
Svet Ganovaf189e32019-02-15 18:45:29 -0800743 private static final int CURRENT_VERSION = 2;
Svet Ganov8455ba22019-01-02 13:05:56 -0800744
745 private final long mBaseSnapshotInterval;
746 private final long mIntervalCompressionMultiplier;
747
748 Persistence(long baseSnapshotInterval, long intervalCompressionMultiplier) {
749 mBaseSnapshotInterval = baseSnapshotInterval;
750 mIntervalCompressionMultiplier = intervalCompressionMultiplier;
751 }
752
Svet Ganov47641072019-06-06 12:57:12 -0700753 private static final AtomicDirectory sHistoricalAppOpsDir = new AtomicDirectory(
Svet Ganov80f500c2019-01-19 17:22:45 -0800754 new File(new File(Environment.getDataSystemDirectory(), "appops"), "history"));
Svet Ganov8455ba22019-01-02 13:05:56 -0800755
756 private File generateFile(@NonNull File baseDir, int depth) {
757 final long globalBeginMillis = computeGlobalIntervalBeginMillis(depth);
758 return new File(baseDir, Long.toString(globalBeginMillis) + HISTORY_FILE_SUFFIX);
759 }
760
Winson470b15b2019-05-07 16:29:59 -0700761 void clearHistoryDLocked(int uid, String packageName) {
762 List<HistoricalOps> historicalOps = readHistoryDLocked();
763
764 if (historicalOps == null) {
765 return;
766 }
767
768 for (int index = 0; index < historicalOps.size(); index++) {
769 historicalOps.get(index).clearHistory(uid, packageName);
770 }
771
772 clearHistoryDLocked();
773
774 persistHistoricalOpsDLocked(historicalOps);
775 }
776
Svet Ganov47641072019-06-06 12:57:12 -0700777 static void clearHistoryDLocked() {
778 sHistoricalAppOpsDir.delete();
Svet Ganov8455ba22019-01-02 13:05:56 -0800779 }
780
781 void persistHistoricalOpsDLocked(@NonNull List<HistoricalOps> ops) {
782 if (DEBUG) {
783 Slog.i(LOG_TAG, "Persisting ops:\n" + opsToDebugString(ops));
784 enforceOpsWellFormed(ops);
785 }
786 try {
Svet Ganov47641072019-06-06 12:57:12 -0700787 final File newBaseDir = sHistoricalAppOpsDir.startWrite();
788 final File oldBaseDir = sHistoricalAppOpsDir.getBackupDirectory();
Svet Ganov80f500c2019-01-19 17:22:45 -0800789 final HistoricalFilesInvariant filesInvariant;
790 if (DEBUG) {
791 filesInvariant = new HistoricalFilesInvariant();
792 filesInvariant.startTracking(oldBaseDir);
793 }
794 final Set<String> oldFileNames = getHistoricalFileNames(oldBaseDir);
795 handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir, ops,
796 oldFileNames, 0);
797 if (DEBUG) {
798 filesInvariant.stopTracking(newBaseDir);
799 }
Svet Ganov47641072019-06-06 12:57:12 -0700800 sHistoricalAppOpsDir.finishWrite();
Svet Ganov8455ba22019-01-02 13:05:56 -0800801 } catch (Throwable t) {
Svet Ganov80f500c2019-01-19 17:22:45 -0800802 wtf("Failed to write historical app ops, restoring backup", t, null);
Svet Ganov47641072019-06-06 12:57:12 -0700803 sHistoricalAppOpsDir.failWrite();
Svet Ganov8455ba22019-01-02 13:05:56 -0800804 }
805 }
806
807 @Nullable List<HistoricalOps> readHistoryRawDLocked() {
808 return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800809 null /*filterPackageName*/, null /*filterAttributionTag*/,
810 null /*filterOpNames*/, 0 /*filter*/, 0 /*filterBeginTimeMills*/,
811 Long.MAX_VALUE /*filterEndTimeMills*/, AppOpsManager.OP_FLAGS_ALL);
Svet Ganov8455ba22019-01-02 13:05:56 -0800812 }
813
814 @Nullable List<HistoricalOps> readHistoryDLocked() {
815 final List<HistoricalOps> result = readHistoryRawDLocked();
816 // Take into account in memory state duration.
817 if (result != null) {
818 final int opCount = result.size();
819 for (int i = 0; i < opCount; i++) {
820 result.get(i).offsetBeginAndEndTime(mBaseSnapshotInterval);
821 }
822 }
823 return result;
824 }
825
826 long getLastPersistTimeMillisDLocked() {
Svet Ganov80f500c2019-01-19 17:22:45 -0800827 File baseDir = null;
Svet Ganov8455ba22019-01-02 13:05:56 -0800828 try {
Svet Ganov47641072019-06-06 12:57:12 -0700829 baseDir = sHistoricalAppOpsDir.startRead();
Svet Ganov80f500c2019-01-19 17:22:45 -0800830 final File[] files = baseDir.listFiles();
831 if (files != null && files.length > 0) {
Svet Ganov47641072019-06-06 12:57:12 -0700832 File shortestFile = null;
833 for (File candidate : files) {
834 final String candidateName = candidate.getName();
835 if (!candidateName.endsWith(HISTORY_FILE_SUFFIX)) {
836 continue;
837 }
838 if (shortestFile == null) {
839 shortestFile = candidate;
840 } else if (candidateName.length() < shortestFile.getName().length()) {
841 shortestFile = candidate;
Svet Ganov80f500c2019-01-19 17:22:45 -0800842 }
843 }
Svet Ganov47641072019-06-06 12:57:12 -0700844 if (shortestFile == null) {
845 return 0;
846 }
847 final String shortestNameNoExtension = shortestFile.getName()
848 .replace(HISTORY_FILE_SUFFIX, "");
849 try {
850 return Long.parseLong(shortestNameNoExtension);
851 } catch (NumberFormatException e) {
852 return 0;
853 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800854 }
Svet Ganov47641072019-06-06 12:57:12 -0700855 sHistoricalAppOpsDir.finishRead();
Svet Ganov80f500c2019-01-19 17:22:45 -0800856 } catch (Throwable e) {
857 wtf("Error reading historical app ops. Deleting history.", e, baseDir);
Svet Ganov47641072019-06-06 12:57:12 -0700858 sHistoricalAppOpsDir.delete();
Svet Ganov8455ba22019-01-02 13:05:56 -0800859 }
860 return 0;
861 }
862
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800863 private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps, int filterUid,
864 @Nullable String filterPackageName, @Nullable String filterAttributionTag,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800865 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
Svet Ganovaf189e32019-02-15 18:45:29 -0800866 long filterBeingMillis, long filterEndMillis, @OpFlags int filterFlags) {
Svet Ganov8455ba22019-01-02 13:05:56 -0800867 final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800868 filterPackageName, filterAttributionTag, filterOpNames, filter,
869 filterBeingMillis, filterEndMillis, filterFlags);
Svet Ganov8455ba22019-01-02 13:05:56 -0800870 if (readOps != null) {
871 final int readCount = readOps.size();
872 for (int i = 0; i < readCount; i++) {
873 final HistoricalOps readOp = readOps.get(i);
874 currentOps.merge(readOp);
875 }
876 }
877 }
878
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800879 private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(int filterUid,
880 @Nullable String filterPackageName, @Nullable String filterAttributionTag,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800881 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
Svet Ganovaf189e32019-02-15 18:45:29 -0800882 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags) {
Svet Ganov80f500c2019-01-19 17:22:45 -0800883 File baseDir = null;
Svet Ganov8455ba22019-01-02 13:05:56 -0800884 try {
Svet Ganov47641072019-06-06 12:57:12 -0700885 baseDir = sHistoricalAppOpsDir.startRead();
Svet Ganov80f500c2019-01-19 17:22:45 -0800886 final HistoricalFilesInvariant filesInvariant;
887 if (DEBUG) {
888 filesInvariant = new HistoricalFilesInvariant();
889 filesInvariant.startTracking(baseDir);
Svet Ganov8455ba22019-01-02 13:05:56 -0800890 }
Svet Ganov80f500c2019-01-19 17:22:45 -0800891 final Set<String> historyFiles = getHistoricalFileNames(baseDir);
Svet Ganov8455ba22019-01-02 13:05:56 -0800892 final long[] globalContentOffsetMillis = {0};
893 final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800894 baseDir, filterUid, filterPackageName, filterAttributionTag, filterOpNames,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800895 filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
896 globalContentOffsetMillis, null /*outOps*/, 0 /*depth*/, historyFiles);
Svet Ganov80f500c2019-01-19 17:22:45 -0800897 if (DEBUG) {
898 filesInvariant.stopTracking(baseDir);
899 }
Svet Ganov47641072019-06-06 12:57:12 -0700900 sHistoricalAppOpsDir.finishRead();
Svet Ganov8455ba22019-01-02 13:05:56 -0800901 return ops;
Svet Ganov80f500c2019-01-19 17:22:45 -0800902 } catch (Throwable t) {
903 wtf("Error reading historical app ops. Deleting history.", t, baseDir);
Svet Ganov47641072019-06-06 12:57:12 -0700904 sHistoricalAppOpsDir.delete();
Svet Ganov8455ba22019-01-02 13:05:56 -0800905 }
906 return null;
907 }
908
909 private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked(
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800910 @NonNull File baseDir, int filterUid, @Nullable String filterPackageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800911 @Nullable String filterAttributionTag, @Nullable String[] filterOpNames,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800912 @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -0800913 long filterEndTimeMillis, @OpFlags int filterFlags,
914 @NonNull long[] globalContentOffsetMillis,
Svet Ganov8455ba22019-01-02 13:05:56 -0800915 @Nullable LinkedList<HistoricalOps> outOps, int depth,
Svet Ganov80f500c2019-01-19 17:22:45 -0800916 @NonNull Set<String> historyFiles)
Svet Ganov8455ba22019-01-02 13:05:56 -0800917 throws IOException, XmlPullParserException {
918 final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
919 depth) * mBaseSnapshotInterval;
920 final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
921 depth + 1) * mBaseSnapshotInterval;
922
923 filterBeginTimeMillis = Math.max(filterBeginTimeMillis - previousIntervalEndMillis, 0);
924 filterEndTimeMillis = filterEndTimeMillis - previousIntervalEndMillis;
925
926 // Read historical data at this level
927 final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir,
928 previousIntervalEndMillis, currentIntervalEndMillis, filterUid,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800929 filterPackageName, filterAttributionTag, filterOpNames, filter,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800930 filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
931 globalContentOffsetMillis, depth, historyFiles);
Svet Ganov8455ba22019-01-02 13:05:56 -0800932 // Empty is a special signal to stop diving
933 if (readOps != null && readOps.isEmpty()) {
934 return outOps;
935 }
936
937 // Collect older historical data from subsequent levels
Philip P. Moltmann4aacd712020-01-03 12:32:20 -0800938 outOps = collectHistoricalOpsRecursiveDLocked(baseDir, filterUid, filterPackageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -0800939 filterAttributionTag, filterOpNames, filter, filterBeginTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -0800940 filterEndTimeMillis, filterFlags, globalContentOffsetMillis, outOps, depth + 1,
Svet Ganov8455ba22019-01-02 13:05:56 -0800941 historyFiles);
942
943 // Make older historical data relative to the current historical level
944 if (outOps != null) {
945 final int opCount = outOps.size();
946 for (int i = 0; i < opCount; i++) {
947 final HistoricalOps collectedOp = outOps.get(i);
948 collectedOp.offsetBeginAndEndTime(currentIntervalEndMillis);
949 }
950 }
951
952 if (readOps != null) {
953 if (outOps == null) {
954 outOps = new LinkedList<>();
955 }
956 // Add the read ops to output
957 final int opCount = readOps.size();
958 for (int i = opCount - 1; i >= 0; i--) {
959 outOps.offerFirst(readOps.get(i));
960 }
961 }
962
963 return outOps;
964 }
965
Svet Ganov8455ba22019-01-02 13:05:56 -0800966 private void handlePersistHistoricalOpsRecursiveDLocked(@NonNull File newBaseDir,
Svet Ganov80f500c2019-01-19 17:22:45 -0800967 @NonNull File oldBaseDir, @Nullable List<HistoricalOps> passedOps,
968 @NonNull Set<String> oldFileNames, int depth)
Svet Ganov8455ba22019-01-02 13:05:56 -0800969 throws IOException, XmlPullParserException {
970 final long previousIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
971 depth) * mBaseSnapshotInterval;
972 final long currentIntervalEndMillis = (long) Math.pow(mIntervalCompressionMultiplier,
973 depth + 1) * mBaseSnapshotInterval;
974
975 if (passedOps == null || passedOps.isEmpty()) {
Svet Ganov80f500c2019-01-19 17:22:45 -0800976 if (!oldFileNames.isEmpty()) {
977 // If there is an old file we need to copy it over to the new state.
978 final File oldFile = generateFile(oldBaseDir, depth);
979 if (oldFileNames.remove(oldFile.getName())) {
980 final File newFile = generateFile(newBaseDir, depth);
981 Files.createLink(newFile.toPath(), oldFile.toPath());
982 }
Svet Ganov8455ba22019-01-02 13:05:56 -0800983 handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
Svet Ganov80f500c2019-01-19 17:22:45 -0800984 passedOps, oldFileNames, depth + 1);
Svet Ganov8455ba22019-01-02 13:05:56 -0800985 }
986 return;
987 }
988
989 if (DEBUG) {
990 enforceOpsWellFormed(passedOps);
991 }
992
993 // Normalize passed ops time to be based off this interval start
994 final int passedOpCount = passedOps.size();
995 for (int i = 0; i < passedOpCount; i++) {
996 final HistoricalOps passedOp = passedOps.get(i);
997 passedOp.offsetBeginAndEndTime(-previousIntervalEndMillis);
998 }
999
1000 if (DEBUG) {
1001 enforceOpsWellFormed(passedOps);
1002 }
1003
1004 // Read persisted ops for this interval
1005 final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir,
1006 previousIntervalEndMillis, currentIntervalEndMillis,
1007 Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001008 null /*filterAttributionTag*/, null /*filterOpNames*/, 0 /*filter*/,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001009 Long.MIN_VALUE /*filterBeginTimeMillis*/,
1010 Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL, null, depth,
1011 null /*historyFiles*/);
Svet Ganov8455ba22019-01-02 13:05:56 -08001012 if (DEBUG) {
1013 enforceOpsWellFormed(existingOps);
1014 }
1015
1016 // Offset existing ops to account for elapsed time
Svet Ganovaf189e32019-02-15 18:45:29 -08001017 if (existingOps != null) {
1018 final int existingOpCount = existingOps.size();
1019 if (existingOpCount > 0) {
1020 // Compute elapsed time
1021 final long elapsedTimeMillis = passedOps.get(passedOps.size() - 1)
Svet Ganov8455ba22019-01-02 13:05:56 -08001022 .getEndTimeMillis();
Svet Ganovaf189e32019-02-15 18:45:29 -08001023 for (int i = 0; i < existingOpCount; i++) {
1024 final HistoricalOps existingOp = existingOps.get(i);
1025 existingOp.offsetBeginAndEndTime(elapsedTimeMillis);
1026 }
Svet Ganov8455ba22019-01-02 13:05:56 -08001027 }
1028 }
1029
1030 if (DEBUG) {
1031 enforceOpsWellFormed(existingOps);
1032 }
1033
1034 final long slotDurationMillis = previousIntervalEndMillis;
1035
1036 // Consolidate passed ops at the current slot duration ensuring each snapshot is
1037 // full. To achieve this we put all passed and existing ops in a list and will
1038 // merge them to ensure each represents a snapshot at the current granularity.
Svet Ganovaf189e32019-02-15 18:45:29 -08001039 final List<HistoricalOps> allOps = new LinkedList<>(passedOps);
1040 if (existingOps != null) {
1041 allOps.addAll(existingOps);
1042 }
Svet Ganov8455ba22019-01-02 13:05:56 -08001043
1044 if (DEBUG) {
1045 enforceOpsWellFormed(allOps);
1046 }
1047
1048 // Compute ops to persist and overflow ops
1049 List<HistoricalOps> persistedOps = null;
1050 List<HistoricalOps> overflowedOps = null;
1051
1052 // We move a snapshot into the next level only if the start time is
1053 // after the end of the current interval. This avoids rewriting all
1054 // files to propagate the information added to the history on every
1055 // iteration. Instead, we would rewrite the next level file only if
1056 // an entire snapshot from the previous level is being propagated.
1057 // The trade off is that we need to store how much the last snapshot
1058 // of the current interval overflows past the interval end. We write
1059 // the overflow data to avoid parsing all snapshots on query.
1060 long intervalOverflowMillis = 0;
1061 final int opCount = allOps.size();
1062 for (int i = 0; i < opCount; i++) {
1063 final HistoricalOps op = allOps.get(i);
1064 final HistoricalOps persistedOp;
1065 final HistoricalOps overflowedOp;
1066 if (op.getEndTimeMillis() <= currentIntervalEndMillis) {
1067 persistedOp = op;
1068 overflowedOp = null;
1069 } else if (op.getBeginTimeMillis() < currentIntervalEndMillis) {
1070 persistedOp = op;
1071 intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
1072 if (intervalOverflowMillis > previousIntervalEndMillis) {
1073 final double splitScale = (double) intervalOverflowMillis
1074 / op.getDurationMillis();
1075 overflowedOp = spliceFromEnd(op, splitScale);
1076 intervalOverflowMillis = op.getEndTimeMillis() - currentIntervalEndMillis;
1077 } else {
1078 overflowedOp = null;
1079 }
1080 } else {
1081 persistedOp = null;
1082 overflowedOp = op;
1083 }
1084 if (persistedOp != null) {
1085 if (persistedOps == null) {
1086 persistedOps = new ArrayList<>();
1087 }
1088 persistedOps.add(persistedOp);
1089 }
1090 if (overflowedOp != null) {
1091 if (overflowedOps == null) {
1092 overflowedOps = new ArrayList<>();
1093 }
1094 overflowedOps.add(overflowedOp);
1095 }
1096 }
1097
1098 if (DEBUG) {
1099 enforceOpsWellFormed(persistedOps);
1100 enforceOpsWellFormed(overflowedOps);
1101 }
1102
Svet Ganov80f500c2019-01-19 17:22:45 -08001103 final File newFile = generateFile(newBaseDir, depth);
1104 oldFileNames.remove(newFile.getName());
1105
Svet Ganov8455ba22019-01-02 13:05:56 -08001106 if (persistedOps != null) {
1107 normalizeSnapshotForSlotDuration(persistedOps, slotDurationMillis);
Svet Ganov8455ba22019-01-02 13:05:56 -08001108 writeHistoricalOpsDLocked(persistedOps, intervalOverflowMillis, newFile);
1109 if (DEBUG) {
Hai Zhangf5387672019-08-13 15:55:58 -07001110 Slog.i(LOG_TAG, "Persisted at depth: " + depth + " file: " + newFile
Svet Ganov8455ba22019-01-02 13:05:56 -08001111 + " ops:\n" + opsToDebugString(persistedOps));
1112 enforceOpsWellFormed(persistedOps);
1113 }
1114 }
1115
1116 handlePersistHistoricalOpsRecursiveDLocked(newBaseDir, oldBaseDir,
Svet Ganov80f500c2019-01-19 17:22:45 -08001117 overflowedOps, oldFileNames, depth + 1);
Svet Ganov8455ba22019-01-02 13:05:56 -08001118 }
1119
Svet Ganovaf189e32019-02-15 18:45:29 -08001120 private @Nullable List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001121 long intervalBeginMillis, long intervalEndMillis, int filterUid,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001122 @Nullable String filterPackageName, @Nullable String filterAttributionTag,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001123 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
Svet Ganovaf189e32019-02-15 18:45:29 -08001124 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags,
Svet Ganov8455ba22019-01-02 13:05:56 -08001125 @Nullable long[] cumulativeOverflowMillis, int depth,
Svet Ganov80f500c2019-01-19 17:22:45 -08001126 @NonNull Set<String> historyFiles)
Svet Ganov8455ba22019-01-02 13:05:56 -08001127 throws IOException, XmlPullParserException {
1128 final File file = generateFile(baseDir, depth);
1129 if (historyFiles != null) {
Svet Ganov80f500c2019-01-19 17:22:45 -08001130 historyFiles.remove(file.getName());
Svet Ganov8455ba22019-01-02 13:05:56 -08001131 }
1132 if (filterBeginTimeMillis >= filterEndTimeMillis
1133 || filterEndTimeMillis < intervalBeginMillis) {
1134 // Don't go deeper
1135 return Collections.emptyList();
1136 }
1137 if (filterBeginTimeMillis >= (intervalEndMillis
1138 + ((intervalEndMillis - intervalBeginMillis) / mIntervalCompressionMultiplier)
1139 + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0))
1140 || !file.exists()) {
1141 if (historyFiles == null || historyFiles.isEmpty()) {
1142 // Don't go deeper
1143 return Collections.emptyList();
1144 } else {
1145 // Keep diving
1146 return null;
1147 }
1148 }
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001149 return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterAttributionTag,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001150 filterOpNames, filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
Svet Ganovaf189e32019-02-15 18:45:29 -08001151 cumulativeOverflowMillis);
Svet Ganov8455ba22019-01-02 13:05:56 -08001152 }
1153
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001154 private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file,
1155 int filterUid, @Nullable String filterPackageName,
1156 @Nullable String filterAttributionTag, @Nullable String[] filterOpNames,
1157 @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
1158 long filterEndTimeMillis, @OpFlags int filterFlags,
Svet Ganov8455ba22019-01-02 13:05:56 -08001159 @Nullable long[] cumulativeOverflowMillis)
1160 throws IOException, XmlPullParserException {
1161 if (DEBUG) {
1162 Slog.i(LOG_TAG, "Reading ops from:" + file);
1163 }
1164 List<HistoricalOps> allOps = null;
1165 try (FileInputStream stream = new FileInputStream(file)) {
1166 final XmlPullParser parser = Xml.newPullParser();
1167 parser.setInput(stream, StandardCharsets.UTF_8.name());
1168 XmlUtils.beginDocument(parser, TAG_HISTORY);
Svet Ganovaf189e32019-02-15 18:45:29 -08001169
1170 // We haven't released version 1 and have more detailed
1171 // accounting - just nuke the current state
1172 final int version = XmlUtils.readIntAttribute(parser, ATTR_VERSION);
1173 if (CURRENT_VERSION == 2 && version < CURRENT_VERSION) {
1174 throw new IllegalStateException("Dropping unsupported history "
1175 + "version 1 for file:" + file);
1176 }
1177
Svet Ganov8455ba22019-01-02 13:05:56 -08001178 final long overflowMillis = XmlUtils.readLongAttribute(parser, ATTR_OVERFLOW, 0);
1179 final int depth = parser.getDepth();
1180 while (XmlUtils.nextElementWithin(parser, depth)) {
1181 if (TAG_OPS.equals(parser.getName())) {
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001182 final HistoricalOps ops = readeHistoricalOpsDLocked(parser, filterUid,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001183 filterPackageName, filterAttributionTag, filterOpNames, filter,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001184 filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
1185 cumulativeOverflowMillis);
Svet Ganov8455ba22019-01-02 13:05:56 -08001186 if (ops == null) {
1187 continue;
1188 }
1189 if (ops.isEmpty()) {
1190 XmlUtils.skipCurrentTag(parser);
1191 continue;
1192 }
1193 if (allOps == null) {
1194 allOps = new ArrayList<>();
1195 }
1196 allOps.add(ops);
1197 }
1198 }
1199 if (cumulativeOverflowMillis != null) {
1200 cumulativeOverflowMillis[0] += overflowMillis;
1201 }
1202 } catch (FileNotFoundException e) {
1203 Slog.i(LOG_TAG, "No history file: " + file.getName());
1204 return Collections.emptyList();
1205 }
1206 if (DEBUG) {
1207 if (allOps != null) {
Hai Zhangf5387672019-08-13 15:55:58 -07001208 Slog.i(LOG_TAG, "Read from file: " + file + " ops:\n"
Svet Ganov8455ba22019-01-02 13:05:56 -08001209 + opsToDebugString(allOps));
1210 enforceOpsWellFormed(allOps);
1211 }
1212 }
1213 return allOps;
1214 }
1215
1216 private @Nullable HistoricalOps readeHistoricalOpsDLocked(
1217 @NonNull XmlPullParser parser, int filterUid, @Nullable String filterPackageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001218 @Nullable String filterAttributionTag, @Nullable String[] filterOpNames,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001219 @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
Svet Ganovaf189e32019-02-15 18:45:29 -08001220 long filterEndTimeMillis, @OpFlags int filterFlags,
1221 @Nullable long[] cumulativeOverflowMillis)
Svet Ganov8455ba22019-01-02 13:05:56 -08001222 throws IOException, XmlPullParserException {
1223 final long beginTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_BEGIN_TIME, 0)
1224 + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
1225 final long endTimeMillis = XmlUtils.readLongAttribute(parser, ATTR_END_TIME, 0)
1226 + (cumulativeOverflowMillis != null ? cumulativeOverflowMillis[0] : 0);
1227 // Keep reading as subsequent records may start matching
1228 if (filterEndTimeMillis < beginTimeMillis) {
1229 return null;
1230 }
1231 // Stop reading as subsequent records will not match
1232 if (filterBeginTimeMillis > endTimeMillis) {
1233 return new HistoricalOps(0, 0);
1234 }
1235 final long filteredBeginTimeMillis = Math.max(beginTimeMillis, filterBeginTimeMillis);
1236 final long filteredEndTimeMillis = Math.min(endTimeMillis, filterEndTimeMillis);
Svet Ganovaf189e32019-02-15 18:45:29 -08001237 // // Keep reading as subsequent records may start matching
1238 // if (filteredEndTimeMillis - filterBeginTimeMillis <= 0) {
1239 // return null;
1240 // }
Svet Ganov8455ba22019-01-02 13:05:56 -08001241 final double filterScale = (double) (filteredEndTimeMillis - filteredBeginTimeMillis)
1242 / (double) (endTimeMillis - beginTimeMillis);
1243 HistoricalOps ops = null;
1244 final int depth = parser.getDepth();
1245 while (XmlUtils.nextElementWithin(parser, depth)) {
1246 if (TAG_UID.equals(parser.getName())) {
1247 final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001248 filterUid, filterPackageName, filterAttributionTag, filterOpNames,
1249 filter, filterFlags, filterScale);
Svet Ganov8455ba22019-01-02 13:05:56 -08001250 if (ops == null) {
1251 ops = returnedOps;
1252 }
1253 }
1254 }
1255 if (ops != null) {
1256 ops.setBeginAndEndTime(filteredBeginTimeMillis, filteredEndTimeMillis);
1257 }
1258 return ops;
1259 }
1260
1261 private @Nullable HistoricalOps readHistoricalUidOpsDLocked(
1262 @Nullable HistoricalOps ops, @NonNull XmlPullParser parser, int filterUid,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001263 @Nullable String filterPackageName, @Nullable String filterAttributionTag,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001264 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
Svet Ganovaf189e32019-02-15 18:45:29 -08001265 @OpFlags int filterFlags, double filterScale)
1266 throws IOException, XmlPullParserException {
Svet Ganov8455ba22019-01-02 13:05:56 -08001267 final int uid = XmlUtils.readIntAttribute(parser, ATTR_NAME);
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001268 if ((filter & FILTER_BY_UID) != 0 && filterUid != uid) {
Svet Ganov8455ba22019-01-02 13:05:56 -08001269 XmlUtils.skipCurrentTag(parser);
1270 return null;
1271 }
1272 final int depth = parser.getDepth();
1273 while (XmlUtils.nextElementWithin(parser, depth)) {
1274 if (TAG_PACKAGE.equals(parser.getName())) {
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001275 final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops, uid,
1276 parser, filterPackageName, filterAttributionTag, filterOpNames, filter,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001277 filterFlags, filterScale);
Svet Ganov8455ba22019-01-02 13:05:56 -08001278 if (ops == null) {
1279 ops = returnedOps;
1280 }
1281 }
1282 }
1283 return ops;
1284 }
1285
1286 private @Nullable HistoricalOps readHistoricalPackageOpsDLocked(
1287 @Nullable HistoricalOps ops, int uid, @NonNull XmlPullParser parser,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001288 @Nullable String filterPackageName, @Nullable String filterAttributionTag,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001289 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
Svet Ganovaf189e32019-02-15 18:45:29 -08001290 @OpFlags int filterFlags, double filterScale)
1291 throws IOException, XmlPullParserException {
Svet Ganov8455ba22019-01-02 13:05:56 -08001292 final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME);
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001293 if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !filterPackageName.equals(packageName)) {
1294 XmlUtils.skipCurrentTag(parser);
1295 return null;
1296 }
1297 final int depth = parser.getDepth();
1298 while (XmlUtils.nextElementWithin(parser, depth)) {
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001299 if (TAG_ATTRIBUTION.equals(parser.getName())) {
1300 final HistoricalOps returnedOps = readHistoricalAttributionOpsDLocked(ops, uid,
1301 packageName, parser, filterAttributionTag, filterOpNames, filter,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001302 filterFlags, filterScale);
1303 if (ops == null) {
1304 ops = returnedOps;
1305 }
1306 }
1307 }
1308 return ops;
1309 }
1310
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001311 private @Nullable HistoricalOps readHistoricalAttributionOpsDLocked(
1312 @Nullable HistoricalOps ops, int uid, String packageName,
1313 @NonNull XmlPullParser parser, @Nullable String filterAttributionTag,
1314 @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
1315 @OpFlags int filterFlags, double filterScale)
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001316 throws IOException, XmlPullParserException {
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001317 final String attributionTag = XmlUtils.readStringAttribute(parser, ATTR_NAME);
1318 if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(filterAttributionTag,
1319 attributionTag)) {
Svet Ganov8455ba22019-01-02 13:05:56 -08001320 XmlUtils.skipCurrentTag(parser);
1321 return null;
1322 }
1323 final int depth = parser.getDepth();
1324 while (XmlUtils.nextElementWithin(parser, depth)) {
1325 if (TAG_OP.equals(parser.getName())) {
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001326 final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid, packageName,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001327 attributionTag, parser, filterOpNames, filter, filterFlags,
1328 filterScale);
Svet Ganov8455ba22019-01-02 13:05:56 -08001329 if (ops == null) {
1330 ops = returnedOps;
1331 }
1332 }
1333 }
1334 return ops;
1335 }
1336
1337 private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001338 int uid, @NonNull String packageName, @Nullable String attributionTag,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001339 @NonNull XmlPullParser parser, @Nullable String[] filterOpNames,
1340 @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags,
1341 double filterScale)
Svet Ganov8455ba22019-01-02 13:05:56 -08001342 throws IOException, XmlPullParserException {
1343 final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME);
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001344 if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(filterOpNames,
Svet Ganov65f1b9e2019-01-17 19:19:40 -08001345 AppOpsManager.opToPublicName(op))) {
Svet Ganov8455ba22019-01-02 13:05:56 -08001346 XmlUtils.skipCurrentTag(parser);
1347 return null;
1348 }
1349 final int depth = parser.getDepth();
1350 while (XmlUtils.nextElementWithin(parser, depth)) {
1351 if (TAG_STATE.equals(parser.getName())) {
Svet Ganovaf189e32019-02-15 18:45:29 -08001352 final HistoricalOps returnedOps = readStateDLocked(ops, uid,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001353 packageName, attributionTag, op, parser, filterFlags, filterScale);
Svet Ganov8455ba22019-01-02 13:05:56 -08001354 if (ops == null) {
1355 ops = returnedOps;
1356 }
1357 }
1358 }
1359 return ops;
1360 }
1361
Svet Ganovaf189e32019-02-15 18:45:29 -08001362 private @Nullable HistoricalOps readStateDLocked(@Nullable HistoricalOps ops,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001363 int uid, @NonNull String packageName, @Nullable String attributionTag, int op,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001364 @NonNull XmlPullParser parser, @OpFlags int filterFlags, double filterScale)
1365 throws IOException {
Svet Ganovaf189e32019-02-15 18:45:29 -08001366 final long key = XmlUtils.readLongAttribute(parser, ATTR_NAME);
1367 final int flags = AppOpsManager.extractFlagsFromKey(key) & filterFlags;
1368 if (flags == 0) {
1369 return null;
1370 }
1371 final int uidState = AppOpsManager.extractUidStateFromKey(key);
Svet Ganov8455ba22019-01-02 13:05:56 -08001372 long accessCount = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_COUNT, 0);
1373 if (accessCount > 0) {
1374 if (!Double.isNaN(filterScale)) {
1375 accessCount = (long) HistoricalOps.round(
1376 (double) accessCount * filterScale);
1377 }
1378 if (ops == null) {
1379 ops = new HistoricalOps(0, 0);
1380 }
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001381 ops.increaseAccessCount(op, uid, packageName, attributionTag, uidState, flags,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001382 accessCount);
Svet Ganov8455ba22019-01-02 13:05:56 -08001383 }
1384 long rejectCount = XmlUtils.readLongAttribute(parser, ATTR_REJECT_COUNT, 0);
1385 if (rejectCount > 0) {
1386 if (!Double.isNaN(filterScale)) {
1387 rejectCount = (long) HistoricalOps.round(
1388 (double) rejectCount * filterScale);
1389 }
1390 if (ops == null) {
1391 ops = new HistoricalOps(0, 0);
1392 }
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001393 ops.increaseRejectCount(op, uid, packageName, attributionTag, uidState, flags,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001394 rejectCount);
Svet Ganov8455ba22019-01-02 13:05:56 -08001395 }
1396 long accessDuration = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_DURATION, 0);
1397 if (accessDuration > 0) {
1398 if (!Double.isNaN(filterScale)) {
1399 accessDuration = (long) HistoricalOps.round(
1400 (double) accessDuration * filterScale);
1401 }
1402 if (ops == null) {
1403 ops = new HistoricalOps(0, 0);
1404 }
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001405 ops.increaseAccessDuration(op, uid, packageName, attributionTag, uidState, flags,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001406 accessDuration);
Svet Ganov8455ba22019-01-02 13:05:56 -08001407 }
1408 return ops;
1409 }
1410
1411 private void writeHistoricalOpsDLocked(@Nullable List<HistoricalOps> allOps,
1412 long intervalOverflowMillis, @NonNull File file) throws IOException {
Svet Ganov47641072019-06-06 12:57:12 -07001413 final FileOutputStream output = sHistoricalAppOpsDir.openWrite(file);
Svet Ganov8455ba22019-01-02 13:05:56 -08001414 try {
1415 final XmlSerializer serializer = Xml.newSerializer();
1416 serializer.setOutput(output, StandardCharsets.UTF_8.name());
1417 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
1418 true);
1419 serializer.startDocument(null, true);
1420 serializer.startTag(null, TAG_HISTORY);
1421 serializer.attribute(null, ATTR_VERSION, String.valueOf(CURRENT_VERSION));
1422 if (intervalOverflowMillis != 0) {
1423 serializer.attribute(null, ATTR_OVERFLOW,
1424 Long.toString(intervalOverflowMillis));
1425 }
1426 if (allOps != null) {
1427 final int opsCount = allOps.size();
1428 for (int i = 0; i < opsCount; i++) {
1429 final HistoricalOps ops = allOps.get(i);
1430 writeHistoricalOpDLocked(ops, serializer);
1431 }
1432 }
1433 serializer.endTag(null, TAG_HISTORY);
1434 serializer.endDocument();
Svet Ganov47641072019-06-06 12:57:12 -07001435 sHistoricalAppOpsDir.closeWrite(output);
Svet Ganov8455ba22019-01-02 13:05:56 -08001436 } catch (IOException e) {
Svet Ganov47641072019-06-06 12:57:12 -07001437 sHistoricalAppOpsDir.failWrite(output);
Svet Ganov8455ba22019-01-02 13:05:56 -08001438 throw e;
1439 }
1440 }
1441
1442 private void writeHistoricalOpDLocked(@NonNull HistoricalOps ops,
1443 @NonNull XmlSerializer serializer) throws IOException {
1444 serializer.startTag(null, TAG_OPS);
1445 serializer.attribute(null, ATTR_BEGIN_TIME, Long.toString(ops.getBeginTimeMillis()));
1446 serializer.attribute(null, ATTR_END_TIME, Long.toString(ops.getEndTimeMillis()));
1447 final int uidCount = ops.getUidCount();
1448 for (int i = 0; i < uidCount; i++) {
1449 final HistoricalUidOps uidOp = ops.getUidOpsAt(i);
1450 writeHistoricalUidOpsDLocked(uidOp, serializer);
1451 }
1452 serializer.endTag(null, TAG_OPS);
1453 }
1454
1455 private void writeHistoricalUidOpsDLocked(@NonNull HistoricalUidOps uidOps,
1456 @NonNull XmlSerializer serializer) throws IOException {
1457 serializer.startTag(null, TAG_UID);
1458 serializer.attribute(null, ATTR_NAME, Integer.toString(uidOps.getUid()));
1459 final int packageCount = uidOps.getPackageCount();
1460 for (int i = 0; i < packageCount; i++) {
1461 final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(i);
1462 writeHistoricalPackageOpsDLocked(packageOps, serializer);
1463 }
1464 serializer.endTag(null, TAG_UID);
1465 }
1466
1467 private void writeHistoricalPackageOpsDLocked(@NonNull HistoricalPackageOps packageOps,
1468 @NonNull XmlSerializer serializer) throws IOException {
1469 serializer.startTag(null, TAG_PACKAGE);
1470 serializer.attribute(null, ATTR_NAME, packageOps.getPackageName());
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001471 final int numAttributions = packageOps.getAttributedOpsCount();
1472 for (int i = 0; i < numAttributions; i++) {
1473 final AppOpsManager.AttributedHistoricalOps op = packageOps.getAttributedOpsAt(i);
1474 writeHistoricalAttributionOpsDLocked(op, serializer);
Svet Ganov8455ba22019-01-02 13:05:56 -08001475 }
1476 serializer.endTag(null, TAG_PACKAGE);
1477 }
1478
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001479 private void writeHistoricalAttributionOpsDLocked(
1480 @NonNull AppOpsManager.AttributedHistoricalOps attributionOps,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001481 @NonNull XmlSerializer serializer) throws IOException {
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001482 serializer.startTag(null, TAG_ATTRIBUTION);
1483 XmlUtils.writeStringAttribute(serializer, ATTR_NAME, attributionOps.getTag());
1484 final int opCount = attributionOps.getOpCount();
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001485 for (int i = 0; i < opCount; i++) {
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001486 final HistoricalOp op = attributionOps.getOpAt(i);
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001487 writeHistoricalOpDLocked(op, serializer);
1488 }
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001489 serializer.endTag(null, TAG_ATTRIBUTION);
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001490 }
1491
Svet Ganov8455ba22019-01-02 13:05:56 -08001492 private void writeHistoricalOpDLocked(@NonNull HistoricalOp op,
1493 @NonNull XmlSerializer serializer) throws IOException {
Svet Ganovaf189e32019-02-15 18:45:29 -08001494 final LongSparseArray keys = op.collectKeys();
1495 if (keys == null || keys.size() <= 0) {
1496 return;
1497 }
Svet Ganov8455ba22019-01-02 13:05:56 -08001498 serializer.startTag(null, TAG_OP);
1499 serializer.attribute(null, ATTR_NAME, Integer.toString(op.getOpCode()));
Svet Ganovaf189e32019-02-15 18:45:29 -08001500 final int keyCount = keys.size();
1501 for (int i = 0; i < keyCount; i++) {
1502 writeStateOnLocked(op, keys.keyAt(i), serializer);
Svet Ganov8455ba22019-01-02 13:05:56 -08001503 }
1504 serializer.endTag(null, TAG_OP);
1505 }
1506
Svet Ganovaf189e32019-02-15 18:45:29 -08001507 private void writeStateOnLocked(@NonNull HistoricalOp op, long key,
Svet Ganov8455ba22019-01-02 13:05:56 -08001508 @NonNull XmlSerializer serializer) throws IOException {
Svet Ganovaf189e32019-02-15 18:45:29 -08001509 final int uidState = AppOpsManager.extractUidStateFromKey(key);
1510 final int flags = AppOpsManager.extractFlagsFromKey(key);
1511
1512 final long accessCount = op.getAccessCount(uidState, uidState, flags);
1513 final long rejectCount = op.getRejectCount(uidState, uidState, flags);
1514 final long accessDuration = op.getAccessDuration(uidState, uidState, flags);
1515
1516 if (accessCount <= 0 && rejectCount <= 0 && accessDuration <= 0) {
Svet Ganov8455ba22019-01-02 13:05:56 -08001517 return;
1518 }
Svet Ganovaf189e32019-02-15 18:45:29 -08001519
Svet Ganov8455ba22019-01-02 13:05:56 -08001520 serializer.startTag(null, TAG_STATE);
Svet Ganovaf189e32019-02-15 18:45:29 -08001521 serializer.attribute(null, ATTR_NAME, Long.toString(key));
Svet Ganov8455ba22019-01-02 13:05:56 -08001522 if (accessCount > 0) {
1523 serializer.attribute(null, ATTR_ACCESS_COUNT, Long.toString(accessCount));
1524 }
1525 if (rejectCount > 0) {
1526 serializer.attribute(null, ATTR_REJECT_COUNT, Long.toString(rejectCount));
1527 }
1528 if (accessDuration > 0) {
1529 serializer.attribute(null, ATTR_ACCESS_DURATION, Long.toString(accessDuration));
1530 }
1531 serializer.endTag(null, TAG_STATE);
1532 }
1533
1534 private static void enforceOpsWellFormed(@NonNull List<HistoricalOps> ops) {
1535 if (ops == null) {
1536 return;
1537 }
1538 HistoricalOps previous;
1539 HistoricalOps current = null;
1540 final int opsCount = ops.size();
1541 for (int i = 0; i < opsCount; i++) {
1542 previous = current;
1543 current = ops.get(i);
1544 if (current.isEmpty()) {
1545 throw new IllegalStateException("Empty ops:\n"
1546 + opsToDebugString(ops));
1547 }
1548 if (current.getEndTimeMillis() < current.getBeginTimeMillis()) {
1549 throw new IllegalStateException("Begin after end:\n"
1550 + opsToDebugString(ops));
1551 }
1552 if (previous != null) {
1553 if (previous.getEndTimeMillis() > current.getBeginTimeMillis()) {
1554 throw new IllegalStateException("Intersecting ops:\n"
1555 + opsToDebugString(ops));
1556 }
1557 if (previous.getBeginTimeMillis() > current.getBeginTimeMillis()) {
1558 throw new IllegalStateException("Non increasing ops:\n"
1559 + opsToDebugString(ops));
1560 }
1561 }
1562 }
1563 }
1564
1565 private long computeGlobalIntervalBeginMillis(int depth) {
1566 long beginTimeMillis = 0;
1567 for (int i = 0; i < depth + 1; i++) {
1568 beginTimeMillis += Math.pow(mIntervalCompressionMultiplier, i);
1569 }
1570 return beginTimeMillis * mBaseSnapshotInterval;
1571 }
1572
1573 private static @NonNull HistoricalOps spliceFromEnd(@NonNull HistoricalOps ops,
1574 double spliceRatio) {
1575 if (DEBUG) {
1576 Slog.w(LOG_TAG, "Splicing from end:" + ops + " ratio:" + spliceRatio);
1577 }
1578 final HistoricalOps splice = ops.spliceFromEnd(spliceRatio);
1579 if (DEBUG) {
1580 Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
1581 }
1582 return splice;
1583 }
1584
1585
1586 private static @NonNull HistoricalOps spliceFromBeginning(@NonNull HistoricalOps ops,
1587 double spliceRatio) {
1588 if (DEBUG) {
1589 Slog.w(LOG_TAG, "Splicing from beginning:" + ops + " ratio:" + spliceRatio);
1590 }
1591 final HistoricalOps splice = ops.spliceFromBeginning(spliceRatio);
1592 if (DEBUG) {
1593 Slog.w(LOG_TAG, "Spliced into:" + ops + " and:" + splice);
1594 }
1595 return splice;
1596 }
1597
1598 private static void normalizeSnapshotForSlotDuration(@NonNull List<HistoricalOps> ops,
1599 long slotDurationMillis) {
1600 if (DEBUG) {
1601 Slog.i(LOG_TAG, "Normalizing for slot duration: " + slotDurationMillis
1602 + " ops:\n" + opsToDebugString(ops));
1603 enforceOpsWellFormed(ops);
1604 }
1605 long slotBeginTimeMillis;
1606 final int opCount = ops.size();
1607 for (int processedIdx = opCount - 1; processedIdx >= 0; processedIdx--) {
1608 final HistoricalOps processedOp = ops.get(processedIdx);
1609 slotBeginTimeMillis = Math.max(processedOp.getEndTimeMillis()
1610 - slotDurationMillis, 0);
1611 for (int candidateIdx = processedIdx - 1; candidateIdx >= 0; candidateIdx--) {
1612 final HistoricalOps candidateOp = ops.get(candidateIdx);
1613 final long candidateSlotIntersectionMillis = candidateOp.getEndTimeMillis()
1614 - Math.min(slotBeginTimeMillis, processedOp.getBeginTimeMillis());
1615 if (candidateSlotIntersectionMillis <= 0) {
1616 break;
1617 }
1618 final float candidateSplitRatio = candidateSlotIntersectionMillis
1619 / (float) candidateOp.getDurationMillis();
1620 if (Float.compare(candidateSplitRatio, 1.0f) >= 0) {
1621 ops.remove(candidateIdx);
1622 processedIdx--;
1623 processedOp.merge(candidateOp);
1624 } else {
1625 final HistoricalOps endSplice = spliceFromEnd(candidateOp,
1626 candidateSplitRatio);
1627 if (endSplice != null) {
1628 processedOp.merge(endSplice);
1629 }
1630 if (candidateOp.isEmpty()) {
1631 ops.remove(candidateIdx);
1632 processedIdx--;
1633 }
1634 }
1635 }
1636 }
1637 if (DEBUG) {
1638 Slog.i(LOG_TAG, "Normalized for slot duration: " + slotDurationMillis
1639 + " ops:\n" + opsToDebugString(ops));
1640 enforceOpsWellFormed(ops);
1641 }
1642 }
1643
1644 private static @NonNull String opsToDebugString(@NonNull List<HistoricalOps> ops) {
1645 StringBuilder builder = new StringBuilder();
1646 final int opCount = ops.size();
1647 for (int i = 0; i < opCount; i++) {
1648 builder.append(" ");
1649 builder.append(ops.get(i));
1650 if (i < opCount - 1) {
1651 builder.append('\n');
1652 }
1653 }
1654 return builder.toString();
1655 }
Svet Ganov80f500c2019-01-19 17:22:45 -08001656
1657 private static Set<String> getHistoricalFileNames(@NonNull File historyDir) {
1658 final File[] files = historyDir.listFiles();
1659 if (files == null) {
1660 return Collections.emptySet();
1661 }
1662 final ArraySet<String> fileNames = new ArraySet<>(files.length);
1663 for (File file : files) {
1664 fileNames.add(file.getName());
1665
1666 }
1667 return fileNames;
1668 }
1669 }
1670
1671 private static class HistoricalFilesInvariant {
1672 private final @NonNull List<File> mBeginFiles = new ArrayList<>();
1673
1674 public void startTracking(@NonNull File folder) {
1675 final File[] files = folder.listFiles();
1676 if (files != null) {
1677 Collections.addAll(mBeginFiles, files);
1678 }
1679 }
1680
1681 public void stopTracking(@NonNull File folder) {
1682 final List<File> endFiles = new ArrayList<>();
1683 final File[] files = folder.listFiles();
1684 if (files != null) {
1685 Collections.addAll(endFiles, files);
1686 }
1687 final long beginOldestFileOffsetMillis = getOldestFileOffsetMillis(mBeginFiles);
1688 final long endOldestFileOffsetMillis = getOldestFileOffsetMillis(endFiles);
1689 if (endOldestFileOffsetMillis < beginOldestFileOffsetMillis) {
1690 final String message = "History loss detected!"
1691 + "\nold files: " + mBeginFiles;
1692 wtf(message, null, folder);
1693 throw new IllegalStateException(message);
1694 }
1695 }
1696
1697 private static long getOldestFileOffsetMillis(@NonNull List<File> files) {
1698 if (files.isEmpty()) {
1699 return 0;
1700 }
1701 String longestName = files.get(0).getName();
1702 final int fileCount = files.size();
1703 for (int i = 1; i < fileCount; i++) {
1704 final File file = files.get(i);
1705 if (file.getName().length() > longestName.length()) {
1706 longestName = file.getName();
1707 }
1708 }
1709 return Long.parseLong(longestName.replace(HISTORY_FILE_SUFFIX, ""));
1710 }
Svet Ganov8455ba22019-01-02 13:05:56 -08001711 }
1712
1713 private final class StringDumpVisitor implements AppOpsManager.HistoricalOpsVisitor {
1714 private final long mNow = System.currentTimeMillis();
1715
1716 private final SimpleDateFormat mDateFormatter = new SimpleDateFormat(
1717 "yyyy-MM-dd HH:mm:ss.SSS");
1718 private final Date mDate = new Date();
1719
1720 private final @NonNull String mOpsPrefix;
1721 private final @NonNull String mUidPrefix;
1722 private final @NonNull String mPackagePrefix;
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001723 private final @NonNull String mAttributionPrefix;
Svet Ganov8455ba22019-01-02 13:05:56 -08001724 private final @NonNull String mEntryPrefix;
1725 private final @NonNull String mUidStatePrefix;
1726 private final @NonNull PrintWriter mWriter;
1727 private final int mFilterUid;
1728 private final String mFilterPackage;
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001729 private final String mFilterAttributionTag;
Svet Ganov8455ba22019-01-02 13:05:56 -08001730 private final int mFilterOp;
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001731 private final @HistoricalOpsRequestFilter int mFilter;
Svet Ganov8455ba22019-01-02 13:05:56 -08001732
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001733 StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer, int filterUid,
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001734 @Nullable String filterPackage, @Nullable String filterAttributionTag, int filterOp,
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001735 @HistoricalOpsRequestFilter int filter) {
Svet Ganov8455ba22019-01-02 13:05:56 -08001736 mOpsPrefix = prefix + " ";
1737 mUidPrefix = mOpsPrefix + " ";
1738 mPackagePrefix = mUidPrefix + " ";
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001739 mAttributionPrefix = mPackagePrefix + " ";
1740 mEntryPrefix = mAttributionPrefix + " ";
Svet Ganov8455ba22019-01-02 13:05:56 -08001741 mUidStatePrefix = mEntryPrefix + " ";
1742 mWriter = writer;
1743 mFilterUid = filterUid;
1744 mFilterPackage = filterPackage;
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001745 mFilterAttributionTag = filterAttributionTag;
Svet Ganov8455ba22019-01-02 13:05:56 -08001746 mFilterOp = filterOp;
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001747 mFilter = filter;
Svet Ganov8455ba22019-01-02 13:05:56 -08001748 }
1749
1750 @Override
1751 public void visitHistoricalOps(HistoricalOps ops) {
1752 mWriter.println();
1753 mWriter.print(mOpsPrefix);
1754 mWriter.println("snapshot:");
1755 mWriter.print(mUidPrefix);
1756 mWriter.print("begin = ");
1757 mDate.setTime(ops.getBeginTimeMillis());
1758 mWriter.print(mDateFormatter.format(mDate));
1759 mWriter.print(" (");
1760 TimeUtils.formatDuration(ops.getBeginTimeMillis() - mNow, mWriter);
1761 mWriter.println(")");
1762 mWriter.print(mUidPrefix);
1763 mWriter.print("end = ");
1764 mDate.setTime(ops.getEndTimeMillis());
1765 mWriter.print(mDateFormatter.format(mDate));
1766 mWriter.print(" (");
1767 TimeUtils.formatDuration(ops.getEndTimeMillis() - mNow, mWriter);
1768 mWriter.println(")");
1769 }
1770
1771 @Override
1772 public void visitHistoricalUidOps(HistoricalUidOps ops) {
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001773 if ((mFilter & FILTER_BY_UID) != 0 && mFilterUid != ops.getUid()) {
Svet Ganov8455ba22019-01-02 13:05:56 -08001774 return;
1775 }
1776 mWriter.println();
1777 mWriter.print(mUidPrefix);
1778 mWriter.print("Uid ");
1779 UserHandle.formatUid(mWriter, ops.getUid());
1780 mWriter.println(":");
1781 }
1782
1783 @Override
1784 public void visitHistoricalPackageOps(HistoricalPackageOps ops) {
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001785 if ((mFilter & FILTER_BY_PACKAGE_NAME) != 0 && !mFilterPackage.equals(
1786 ops.getPackageName())) {
Svet Ganov8455ba22019-01-02 13:05:56 -08001787 return;
1788 }
1789 mWriter.print(mPackagePrefix);
1790 mWriter.print("Package ");
1791 mWriter.print(ops.getPackageName());
1792 mWriter.println(":");
1793 }
1794
1795 @Override
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001796 public void visitHistoricalAttributionOps(AppOpsManager.AttributedHistoricalOps ops) {
1797 if ((mFilter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(mFilterPackage,
1798 ops.getTag())) {
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001799 return;
1800 }
Philip P. Moltmann12ac3f42020-03-05 15:01:29 -08001801 mWriter.print(mAttributionPrefix);
1802 mWriter.print("Attribution ");
1803 mWriter.print(ops.getTag());
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001804 mWriter.println(":");
1805 }
1806
1807 @Override
Svet Ganov8455ba22019-01-02 13:05:56 -08001808 public void visitHistoricalOp(HistoricalOp ops) {
Philip P. Moltmann4aacd712020-01-03 12:32:20 -08001809 if ((mFilter & FILTER_BY_OP_NAMES) != 0 && mFilterOp != ops.getOpCode()) {
Svet Ganov8455ba22019-01-02 13:05:56 -08001810 return;
1811 }
1812 mWriter.print(mEntryPrefix);
1813 mWriter.print(AppOpsManager.opToName(ops.getOpCode()));
1814 mWriter.println(":");
Svet Ganovaf189e32019-02-15 18:45:29 -08001815 final LongSparseArray keys = ops.collectKeys();
1816 final int keyCount = keys.size();
1817 for (int i = 0; i < keyCount; i++) {
1818 final long key = keys.keyAt(i);
1819 final int uidState = AppOpsManager.extractUidStateFromKey(key);
1820 final int flags = AppOpsManager.extractFlagsFromKey(key);
Svet Ganov8455ba22019-01-02 13:05:56 -08001821 boolean printedUidState = false;
Svet Ganovaf189e32019-02-15 18:45:29 -08001822 final long accessCount = ops.getAccessCount(uidState, uidState, flags);
Svet Ganov8455ba22019-01-02 13:05:56 -08001823 if (accessCount > 0) {
1824 if (!printedUidState) {
1825 mWriter.print(mUidStatePrefix);
Svet Ganovaf189e32019-02-15 18:45:29 -08001826 mWriter.print(AppOpsManager.keyToString(key));
Joel Galenson775a8402019-01-14 15:23:15 -08001827 mWriter.print(" = ");
Svet Ganov8455ba22019-01-02 13:05:56 -08001828 printedUidState = true;
1829 }
1830 mWriter.print("access=");
1831 mWriter.print(accessCount);
1832 }
Svet Ganovaf189e32019-02-15 18:45:29 -08001833 final long rejectCount = ops.getRejectCount(uidState, uidState, flags);
Svet Ganov8455ba22019-01-02 13:05:56 -08001834 if (rejectCount > 0) {
1835 if (!printedUidState) {
1836 mWriter.print(mUidStatePrefix);
Svet Ganovaf189e32019-02-15 18:45:29 -08001837 mWriter.print(AppOpsManager.keyToString(key));
Joel Galenson775a8402019-01-14 15:23:15 -08001838 mWriter.print(" = ");
Svet Ganov8455ba22019-01-02 13:05:56 -08001839 printedUidState = true;
1840 } else {
Joel Galenson775a8402019-01-14 15:23:15 -08001841 mWriter.print(", ");
Svet Ganov8455ba22019-01-02 13:05:56 -08001842 }
1843 mWriter.print("reject=");
1844 mWriter.print(rejectCount);
1845 }
Svet Ganovaf189e32019-02-15 18:45:29 -08001846 final long accessDuration = ops.getAccessDuration(uidState, uidState, flags);
Svet Ganov8455ba22019-01-02 13:05:56 -08001847 if (accessDuration > 0) {
1848 if (!printedUidState) {
1849 mWriter.print(mUidStatePrefix);
Svet Ganovaf189e32019-02-15 18:45:29 -08001850 mWriter.print(AppOpsManager.keyToString(key));
Joel Galenson775a8402019-01-14 15:23:15 -08001851 mWriter.print(" = ");
Svet Ganov8455ba22019-01-02 13:05:56 -08001852 printedUidState = true;
1853 } else {
Joel Galenson775a8402019-01-14 15:23:15 -08001854 mWriter.print(", ");
Svet Ganov8455ba22019-01-02 13:05:56 -08001855 }
1856 mWriter.print("duration=");
Joel Galenson775a8402019-01-14 15:23:15 -08001857 TimeUtils.formatDuration(accessDuration, mWriter);
Svet Ganov8455ba22019-01-02 13:05:56 -08001858 }
1859 if (printedUidState) {
Joel Galenson775a8402019-01-14 15:23:15 -08001860 mWriter.println("");
Svet Ganov8455ba22019-01-02 13:05:56 -08001861 }
1862 }
1863 }
1864 }
Svet Ganov80f500c2019-01-19 17:22:45 -08001865
1866 private static void wtf(@Nullable String message, @Nullable Throwable t,
1867 @Nullable File storage) {
1868 Slog.wtf(LOG_TAG, message, t);
1869 if (KEEP_WTF_LOG) {
1870 try {
1871 final File file = new File(new File(Environment.getDataSystemDirectory(), "appops"),
1872 "wtf" + TimeUtils.formatForLogging(System.currentTimeMillis()));
1873 if (file.createNewFile()) {
1874 try (PrintWriter writer = new PrintWriter(file)) {
1875 if (t != null) {
1876 writer.append('\n').append(t.toString());
1877 }
1878 writer.append('\n').append(Debug.getCallers(10));
1879 if (storage != null) {
1880 writer.append("\nfiles: " + Arrays.toString(storage.listFiles()));
1881 } else {
1882 writer.append("\nfiles: none");
1883 }
1884 }
1885 }
1886 } catch (IOException e) {
1887 /* ignore */
1888 }
1889 }
1890 }
Svet Ganov8455ba22019-01-02 13:05:56 -08001891}