1 /**
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16 
17 package com.android.server.usage;
18 
19 import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
20 import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
21 import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
22 import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
23 import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
24 import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
25 import static android.app.usage.UsageStatsManager.REASON_SUB_MASK;
26 import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
27 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
28 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
29 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
30 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
31 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
32 
33 import static com.android.server.usage.AppStandbyController.isUserUsage;
34 
35 import android.annotation.CurrentTimeMillisLong;
36 import android.annotation.ElapsedRealtimeLong;
37 import android.app.usage.AppStandbyInfo;
38 import android.app.usage.UsageStatsManager;
39 import android.os.SystemClock;
40 import android.util.ArrayMap;
41 import android.util.AtomicFile;
42 import android.util.IndentingPrintWriter;
43 import android.util.Slog;
44 import android.util.SparseArray;
45 import android.util.SparseLongArray;
46 import android.util.TimeUtils;
47 import android.util.Xml;
48 
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.internal.util.CollectionUtils;
51 import com.android.internal.util.FastXmlSerializer;
52 import com.android.internal.util.FrameworkStatsLog;
53 import com.android.internal.util.XmlUtils;
54 
55 import libcore.io.IoUtils;
56 
57 import org.xmlpull.v1.XmlPullParser;
58 import org.xmlpull.v1.XmlPullParserException;
59 
60 import java.io.BufferedOutputStream;
61 import java.io.BufferedReader;
62 import java.io.File;
63 import java.io.FileInputStream;
64 import java.io.FileOutputStream;
65 import java.io.FileReader;
66 import java.io.IOException;
67 import java.nio.charset.StandardCharsets;
68 import java.util.ArrayList;
69 import java.util.List;
70 
71 /**
72  * Keeps track of recent active state changes in apps.
73  * Access should be guarded by a lock by the caller.
74  */
75 public class AppIdleHistory {
76 
77     private static final String TAG = "AppIdleHistory";
78 
79     private static final boolean DEBUG = AppStandbyController.DEBUG;
80 
81     // History for all users and all packages
82     private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
83     private static final long ONE_MINUTE = 60 * 1000;
84 
85     static final int STANDBY_BUCKET_UNKNOWN = -1;
86 
87     /**
88      * The bucket beyond which apps are considered idle. Any apps in this bucket or lower are
89      * considered idle while those in higher buckets are not considered idle.
90      */
91     static final int IDLE_BUCKET_CUTOFF = STANDBY_BUCKET_RARE;
92 
93     /** Initial version of the xml containing the app idle stats ({@link #APP_IDLE_FILENAME}). */
94     private static final int XML_VERSION_INITIAL = 0;
95     /**
96      * Allowed writing expiry times for any standby bucket instead of only active and working set.
97      * In previous version, we used to specify expiry times for active and working set as
98      * attributes:
99      * <pre>
100      *     <package activeTimeoutTime="..." workingSetTimeoutTime="..." />
101      * </pre>
102      * In this version, it is changed to:
103      * <pre>
104      *     <package>
105      *         <expiryTimes>
106      *             <item bucket="..." expiry="..." />
107      *             <item bucket="..." expiry="..." />
108      *         </expiryTimes>
109      *     </package>
110      * </pre>
111      */
112     private static final int XML_VERSION_ADD_BUCKET_EXPIRY_TIMES = 1;
113     /** Current version */
114     private static final int XML_VERSION_CURRENT = XML_VERSION_ADD_BUCKET_EXPIRY_TIMES;
115 
116     @VisibleForTesting
117     static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
118     private static final String TAG_PACKAGES = "packages";
119     private static final String TAG_PACKAGE = "package";
120     private static final String TAG_BUCKET_EXPIRY_TIMES = "expiryTimes";
121     private static final String TAG_ITEM = "item";
122     private static final String ATTR_NAME = "name";
123     // Screen on timebase time when app was last used
124     private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
125     // Elapsed timebase time when app was last used
126     private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime";
127     // Elapsed timebase time when app was last used by the user
128     private static final String ATTR_LAST_USED_BY_USER_ELAPSED = "lastUsedByUserElapsedTime";
129     // Elapsed timebase time when the app bucket was last predicted externally
130     private static final String ATTR_LAST_PREDICTED_TIME = "lastPredictedTime";
131     // The standby bucket for the app
132     private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
133     // The reason the app was put in the above bucket
134     private static final String ATTR_BUCKETING_REASON = "bucketReason";
135     // The last time a job was run for this app
136     private static final String ATTR_LAST_RUN_JOB_TIME = "lastJobRunTime";
137     // The time when the forced active state can be overridden.
138     private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime";
139     // The time when the forced working_set state can be overridden.
140     private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime";
141     // The standby bucket value
142     private static final String ATTR_BUCKET = "bucket";
143     // The time when the forced bucket state can be overridde.
144     private static final String ATTR_EXPIRY_TIME = "expiry";
145     // Elapsed timebase time when the app was last marked for restriction.
146     private static final String ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED =
147             "lastRestrictionAttemptElapsedTime";
148     // Reason why the app was last marked for restriction.
149     private static final String ATTR_LAST_RESTRICTION_ATTEMPT_REASON =
150             "lastRestrictionAttemptReason";
151     // The next estimated launch time of the app, in ms since epoch.
152     private static final String ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME = "nextEstimatedAppLaunchTime";
153     // Version of the xml file.
154     private static final String ATTR_VERSION = "version";
155 
156     // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
157     private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
158     private long mElapsedDuration; // Total device on duration since device was "born"
159 
160     // screen on time = mScreenOnDuration + (timeNow - mScreenOnSnapshot)
161     private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration
162     private long mScreenOnDuration; // Total screen on duration since device was "born"
163 
164     private final File mStorageDir;
165 
166     private boolean mScreenOn;
167 
168     static class AppUsageHistory {
169         // Last used time (including system usage), using elapsed timebase
170         long lastUsedElapsedTime;
171         // Last time the user used the app, using elapsed timebase
172         long lastUsedByUserElapsedTime;
173         // Last used time using screen_on timebase
174         long lastUsedScreenTime;
175         // Last predicted time using elapsed timebase
176         long lastPredictedTime;
177         // Last predicted bucket
178         @UsageStatsManager.StandbyBuckets
179         int lastPredictedBucket = STANDBY_BUCKET_UNKNOWN;
180         // Standby bucket
181         @UsageStatsManager.StandbyBuckets
182         int currentBucket;
183         // Reason for setting the standby bucket. The value here is a combination of
184         // one of UsageStatsManager.REASON_MAIN_* and one (or none) of
185         // UsageStatsManager.REASON_SUB_*. Also see REASON_MAIN_MASK and REASON_SUB_MASK.
186         int bucketingReason;
187         // In-memory only, last bucket for which the listeners were informed
188         int lastInformedBucket;
189         // The last time a job was run for this app, using elapsed timebase
190         long lastJobRunTime;
191         // The estimated time the app will be launched next, in milliseconds since epoch.
192         @CurrentTimeMillisLong
193         long nextEstimatedLaunchTime;
194         // Contains standby buckets that apps were forced into and the corresponding expiry times
195         // (in elapsed timebase) for each bucket state. App will stay in the highest bucket until
196         // it's expiry time is elapsed and will be moved to the next highest bucket.
197         SparseLongArray bucketExpiryTimesMs;
198         // The last time an agent attempted to put the app into the RESTRICTED bucket.
199         long lastRestrictAttemptElapsedTime;
200         // The last reason the app was marked to be put into the RESTRICTED bucket.
201         int lastRestrictReason;
202     }
203 
AppIdleHistory(File storageDir, long elapsedRealtime)204     AppIdleHistory(File storageDir, long elapsedRealtime) {
205         mElapsedSnapshot = elapsedRealtime;
206         mScreenOnSnapshot = elapsedRealtime;
207         mStorageDir = storageDir;
208         readScreenOnTime();
209     }
210 
updateDisplay(boolean screenOn, long elapsedRealtime)211     public void updateDisplay(boolean screenOn, long elapsedRealtime) {
212         if (screenOn == mScreenOn) return;
213 
214         mScreenOn = screenOn;
215         if (mScreenOn) {
216             mScreenOnSnapshot = elapsedRealtime;
217         } else {
218             mScreenOnDuration += elapsedRealtime - mScreenOnSnapshot;
219             mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
220             mElapsedSnapshot = elapsedRealtime;
221         }
222         if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot
223                 + ", mScreenOnDuration=" + mScreenOnDuration
224                 + ", mScreenOn=" + mScreenOn);
225     }
226 
getScreenOnTime(long elapsedRealtime)227     public long getScreenOnTime(long elapsedRealtime) {
228         long screenOnTime = mScreenOnDuration;
229         if (mScreenOn) {
230             screenOnTime += elapsedRealtime - mScreenOnSnapshot;
231         }
232         return screenOnTime;
233     }
234 
235     @VisibleForTesting
getScreenOnTimeFile()236     File getScreenOnTimeFile() {
237         return new File(mStorageDir, "screen_on_time");
238     }
239 
readScreenOnTime()240     private void readScreenOnTime() {
241         File screenOnTimeFile = getScreenOnTimeFile();
242         if (screenOnTimeFile.exists()) {
243             try {
244                 BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile));
245                 mScreenOnDuration = Long.parseLong(reader.readLine());
246                 mElapsedDuration = Long.parseLong(reader.readLine());
247                 reader.close();
248             } catch (IOException | NumberFormatException e) {
249             }
250         } else {
251             writeScreenOnTime();
252         }
253     }
254 
writeScreenOnTime()255     private void writeScreenOnTime() {
256         AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile());
257         FileOutputStream fos = null;
258         try {
259             fos = screenOnTimeFile.startWrite();
260             fos.write((Long.toString(mScreenOnDuration) + "\n"
261                     + Long.toString(mElapsedDuration) + "\n").getBytes());
262             screenOnTimeFile.finishWrite(fos);
263         } catch (IOException ioe) {
264             screenOnTimeFile.failWrite(fos);
265         }
266     }
267 
268     /**
269      * To be called periodically to keep track of elapsed time when app idle times are written
270      */
writeAppIdleDurations()271     public void writeAppIdleDurations() {
272         final long elapsedRealtime = SystemClock.elapsedRealtime();
273         // Only bump up and snapshot the elapsed time. Don't change screen on duration.
274         mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
275         mElapsedSnapshot = elapsedRealtime;
276         writeScreenOnTime();
277     }
278 
279     /**
280      * Mark the app as used and update the bucket if necessary. If there is a expiry time specified
281      * that's in the future, then the usage event is temporary and keeps the app in the specified
282      * bucket at least until the expiry time is reached. This can be used to keep the app in an
283      * elevated bucket for a while until some important task gets to run.
284      *
285      * @param appUsageHistory the usage record for the app being updated
286      * @param packageName name of the app being updated, for logging purposes
287      * @param newBucket the bucket to set the app to
288      * @param usageReason the sub-reason for usage, one of REASON_SUB_USAGE_*
289      * @param nowElapsedRealtimeMs mark as used time if non-zero (in
290      *                          {@link SystemClock#elapsedRealtime()} time base)
291      * @param expiryElapsedRealtimeMs the expiry time for the specified bucket (in
292      *                         {@link SystemClock#elapsedRealtime()} time base)
293      * @return {@code appUsageHistory}
294      */
reportUsage(AppUsageHistory appUsageHistory, String packageName, int userId, int newBucket, int usageReason, long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs)295     AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int userId,
296             int newBucket, int usageReason,
297             long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs) {
298         int bucketingReason = REASON_MAIN_USAGE | usageReason;
299         final boolean isUserUsage = isUserUsage(bucketingReason);
300 
301         if (appUsageHistory.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage
302                 && (appUsageHistory.bucketingReason & REASON_MAIN_MASK) != REASON_MAIN_TIMEOUT) {
303             // Only user usage should bring an app out of the RESTRICTED bucket, unless the app
304             // just timed out into RESTRICTED.
305             newBucket = STANDBY_BUCKET_RESTRICTED;
306             bucketingReason = appUsageHistory.bucketingReason;
307         } else {
308             // Set the expiry time if applicable
309             if (expiryElapsedRealtimeMs > nowElapsedRealtimeMs) {
310                 // Convert to elapsed timebase
311                 final long expiryTimeMs = getElapsedTime(expiryElapsedRealtimeMs);
312                 if (appUsageHistory.bucketExpiryTimesMs == null) {
313                     appUsageHistory.bucketExpiryTimesMs = new SparseLongArray();
314                 }
315                 final long currentExpiryTimeMs = appUsageHistory.bucketExpiryTimesMs.get(newBucket);
316                 appUsageHistory.bucketExpiryTimesMs.put(newBucket,
317                         Math.max(expiryTimeMs, currentExpiryTimeMs));
318                 removeElapsedExpiryTimes(appUsageHistory, getElapsedTime(nowElapsedRealtimeMs));
319             }
320         }
321 
322         if (nowElapsedRealtimeMs != 0) {
323             appUsageHistory.lastUsedElapsedTime = mElapsedDuration
324                     + (nowElapsedRealtimeMs - mElapsedSnapshot);
325             if (isUserUsage) {
326                 appUsageHistory.lastUsedByUserElapsedTime = appUsageHistory.lastUsedElapsedTime;
327             }
328             appUsageHistory.lastUsedScreenTime = getScreenOnTime(nowElapsedRealtimeMs);
329         }
330 
331         if (appUsageHistory.currentBucket >= newBucket) {
332             if (appUsageHistory.currentBucket > newBucket) {
333                 appUsageHistory.currentBucket = newBucket;
334                 logAppStandbyBucketChanged(packageName, userId, newBucket, bucketingReason);
335             }
336             appUsageHistory.bucketingReason = bucketingReason;
337         }
338 
339         return appUsageHistory;
340     }
341 
removeElapsedExpiryTimes(AppUsageHistory appUsageHistory, long elapsedTimeMs)342     private void removeElapsedExpiryTimes(AppUsageHistory appUsageHistory, long elapsedTimeMs) {
343         if (appUsageHistory.bucketExpiryTimesMs == null) {
344             return;
345         }
346         for (int i = appUsageHistory.bucketExpiryTimesMs.size() - 1; i >= 0; --i) {
347             if (appUsageHistory.bucketExpiryTimesMs.valueAt(i) < elapsedTimeMs) {
348                 appUsageHistory.bucketExpiryTimesMs.removeAt(i);
349             }
350         }
351     }
352 
353     /**
354      * Mark the app as used and update the bucket if necessary. If there is a expiry time specified
355      * that's in the future, then the usage event is temporary and keeps the app in the specified
356      * bucket at least until the expiry time is reached. This can be used to keep the app in an
357      * elevated bucket for a while until some important task gets to run.
358      *
359      * @param packageName package name of the app the usage is reported for
360      * @param userId user that the app is running in
361      * @param newBucket the bucket to set the app to
362      * @param usageReason sub reason for usage
363      * @param nowElapsedRealtimeMs mark as used time if non-zero (in
364      *                             {@link SystemClock#elapsedRealtime()} time base).
365      * @param expiryElapsedRealtimeMs the expiry time for the specified bucket (in
366      *                         {@link SystemClock#elapsedRealtime()} time base).
367      * @return the {@link AppUsageHistory} corresponding to the {@code packageName}
368      *         and {@code userId}.
369      */
reportUsage(String packageName, int userId, int newBucket, int usageReason, long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs)370     public AppUsageHistory reportUsage(String packageName, int userId, int newBucket,
371             int usageReason, long nowElapsedRealtimeMs, long expiryElapsedRealtimeMs) {
372         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
373         AppUsageHistory history = getPackageHistory(userHistory, packageName,
374                 nowElapsedRealtimeMs, true);
375         return reportUsage(history, packageName, userId, newBucket, usageReason,
376                 nowElapsedRealtimeMs, expiryElapsedRealtimeMs);
377     }
378 
getUserHistory(int userId)379     private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
380         ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
381         if (userHistory == null) {
382             userHistory = new ArrayMap<>();
383             mIdleHistory.put(userId, userHistory);
384             readAppIdleTimes(userId, userHistory);
385         }
386         return userHistory;
387     }
388 
389     // TODO (206518483): Remove unused parameter 'elapsedRealtime'.
getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory, String packageName, long elapsedRealtime, boolean create)390     private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory,
391             String packageName, long elapsedRealtime, boolean create) {
392         AppUsageHistory appUsageHistory = userHistory.get(packageName);
393         if (appUsageHistory == null && create) {
394             appUsageHistory = new AppUsageHistory();
395             appUsageHistory.lastUsedByUserElapsedTime = Integer.MIN_VALUE;
396             appUsageHistory.lastUsedElapsedTime = Integer.MIN_VALUE;
397             appUsageHistory.lastUsedScreenTime = Integer.MIN_VALUE;
398             appUsageHistory.lastPredictedTime = Integer.MIN_VALUE;
399             appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER;
400             appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT;
401             appUsageHistory.lastInformedBucket = -1;
402             appUsageHistory.lastJobRunTime = Long.MIN_VALUE; // long long time ago
403             userHistory.put(packageName, appUsageHistory);
404         }
405         return appUsageHistory;
406     }
407 
onUserRemoved(int userId)408     public void onUserRemoved(int userId) {
409         mIdleHistory.remove(userId);
410     }
411 
isIdle(String packageName, int userId, long elapsedRealtime)412     public boolean isIdle(String packageName, int userId, long elapsedRealtime) {
413         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
414         AppUsageHistory appUsageHistory =
415                 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
416         return appUsageHistory.currentBucket >= IDLE_BUCKET_CUTOFF;
417     }
418 
getAppUsageHistory(String packageName, int userId, long elapsedRealtime)419     public AppUsageHistory getAppUsageHistory(String packageName, int userId,
420             long elapsedRealtime) {
421         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
422         AppUsageHistory appUsageHistory =
423                 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
424         return appUsageHistory;
425     }
426 
setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, int bucket, int reason)427     public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
428             int bucket, int reason) {
429         setAppStandbyBucket(packageName, userId, elapsedRealtime, bucket, reason, false);
430     }
431 
setAppStandbyBucket(String packageName, int userId, long elapsedRealtime, int bucket, int reason, boolean resetExpiryTimes)432     public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
433             int bucket, int reason, boolean resetExpiryTimes) {
434         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
435         AppUsageHistory appUsageHistory =
436                 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
437         final boolean changed = appUsageHistory.currentBucket != bucket;
438         appUsageHistory.currentBucket = bucket;
439         appUsageHistory.bucketingReason = reason;
440 
441         final long elapsed = getElapsedTime(elapsedRealtime);
442 
443         if ((reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED) {
444             appUsageHistory.lastPredictedTime = elapsed;
445             appUsageHistory.lastPredictedBucket = bucket;
446         }
447         if (resetExpiryTimes && appUsageHistory.bucketExpiryTimesMs != null) {
448             appUsageHistory.bucketExpiryTimesMs.clear();
449         }
450         if (changed) {
451             logAppStandbyBucketChanged(packageName, userId, bucket, reason);
452         }
453     }
454 
455     /**
456      * Update the prediction for the app but don't change the actual bucket
457      * @param app The app for which the prediction was made
458      * @param elapsedTimeAdjusted The elapsed time in the elapsed duration timebase
459      * @param bucket The predicted bucket
460      */
updateLastPrediction(AppUsageHistory app, long elapsedTimeAdjusted, int bucket)461     public void updateLastPrediction(AppUsageHistory app, long elapsedTimeAdjusted, int bucket) {
462         app.lastPredictedTime = elapsedTimeAdjusted;
463         app.lastPredictedBucket = bucket;
464     }
465 
466     /**
467      * Marks the next time the app is expected to be launched, in the current millis timebase.
468      */
setEstimatedLaunchTime(String packageName, int userId, @ElapsedRealtimeLong long nowElapsed, @CurrentTimeMillisLong long launchTime)469     public void setEstimatedLaunchTime(String packageName, int userId,
470             @ElapsedRealtimeLong long nowElapsed, @CurrentTimeMillisLong long launchTime) {
471         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
472         AppUsageHistory appUsageHistory =
473                 getPackageHistory(userHistory, packageName, nowElapsed, true);
474         appUsageHistory.nextEstimatedLaunchTime = launchTime;
475     }
476 
477     /**
478      * Marks the last time a job was run, with the given elapsedRealtime. The time stored is
479      * based on the elapsed timebase.
480      * @param packageName
481      * @param userId
482      * @param elapsedRealtime
483      */
setLastJobRunTime(String packageName, int userId, long elapsedRealtime)484     public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
485         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
486         AppUsageHistory appUsageHistory =
487                 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
488         appUsageHistory.lastJobRunTime = getElapsedTime(elapsedRealtime);
489     }
490 
491     /**
492      * Notes an attempt to put the app in the {@link UsageStatsManager#STANDBY_BUCKET_RESTRICTED}
493      * bucket.
494      *
495      * @param packageName     The package name of the app that is being restricted
496      * @param userId          The ID of the user in which the app is being restricted
497      * @param elapsedRealtime The time the attempt was made, in the (unadjusted) elapsed realtime
498      *                        timebase
499      * @param reason          The reason for the restriction attempt
500      */
noteRestrictionAttempt(String packageName, int userId, long elapsedRealtime, int reason)501     void noteRestrictionAttempt(String packageName, int userId, long elapsedRealtime, int reason) {
502         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
503         AppUsageHistory appUsageHistory =
504                 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
505         appUsageHistory.lastRestrictAttemptElapsedTime = getElapsedTime(elapsedRealtime);
506         appUsageHistory.lastRestrictReason = reason;
507     }
508 
509     /**
510      * Returns the next estimated launch time of this app. Will return {@link Long#MAX_VALUE} if
511      * there's no estimated time.
512      */
513     @CurrentTimeMillisLong
getEstimatedLaunchTime(String packageName, int userId, long nowElapsed)514     public long getEstimatedLaunchTime(String packageName, int userId, long nowElapsed) {
515         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
516         AppUsageHistory appUsageHistory =
517                 getPackageHistory(userHistory, packageName, nowElapsed, false);
518         // Don't adjust the default, else it'll wrap around to a positive value
519         if (appUsageHistory == null
520                 || appUsageHistory.nextEstimatedLaunchTime < System.currentTimeMillis()) {
521             return Long.MAX_VALUE;
522         }
523         return appUsageHistory.nextEstimatedLaunchTime;
524     }
525 
526     /**
527      * Returns the time since the last job was run for this app. This can be larger than the
528      * current elapsedRealtime, in case it happened before boot or a really large value if no jobs
529      * were ever run.
530      * @param packageName
531      * @param userId
532      * @param elapsedRealtime
533      * @return
534      */
getTimeSinceLastJobRun(String packageName, int userId, long elapsedRealtime)535     public long getTimeSinceLastJobRun(String packageName, int userId, long elapsedRealtime) {
536         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
537         AppUsageHistory appUsageHistory =
538                 getPackageHistory(userHistory, packageName, elapsedRealtime, false);
539         // Don't adjust the default, else it'll wrap around to a positive value
540         if (appUsageHistory == null || appUsageHistory.lastJobRunTime == Long.MIN_VALUE) {
541             return Long.MAX_VALUE;
542         }
543         return getElapsedTime(elapsedRealtime) - appUsageHistory.lastJobRunTime;
544     }
545 
getTimeSinceLastUsedByUser(String packageName, int userId, long elapsedRealtime)546     public long getTimeSinceLastUsedByUser(String packageName, int userId, long elapsedRealtime) {
547         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
548         AppUsageHistory appUsageHistory =
549                 getPackageHistory(userHistory, packageName, elapsedRealtime, false);
550         if (appUsageHistory == null || appUsageHistory.lastUsedByUserElapsedTime == Long.MIN_VALUE
551                 || appUsageHistory.lastUsedByUserElapsedTime <= 0) {
552             return Long.MAX_VALUE;
553         }
554         return getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedByUserElapsedTime;
555     }
556 
getAppStandbyBucket(String packageName, int userId, long elapsedRealtime)557     public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
558         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
559         AppUsageHistory appUsageHistory =
560                 getPackageHistory(userHistory, packageName, elapsedRealtime, false);
561         return appUsageHistory == null ? STANDBY_BUCKET_NEVER : appUsageHistory.currentBucket;
562     }
563 
getAppStandbyBuckets(int userId, boolean appIdleEnabled)564     public ArrayList<AppStandbyInfo> getAppStandbyBuckets(int userId, boolean appIdleEnabled) {
565         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
566         int size = userHistory.size();
567         ArrayList<AppStandbyInfo> buckets = new ArrayList<>(size);
568         for (int i = 0; i < size; i++) {
569             buckets.add(new AppStandbyInfo(userHistory.keyAt(i),
570                     appIdleEnabled ? userHistory.valueAt(i).currentBucket : STANDBY_BUCKET_ACTIVE));
571         }
572         return buckets;
573     }
574 
getAppStandbyReason(String packageName, int userId, long elapsedRealtime)575     public int getAppStandbyReason(String packageName, int userId, long elapsedRealtime) {
576         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
577         AppUsageHistory appUsageHistory =
578                 getPackageHistory(userHistory, packageName, elapsedRealtime, false);
579         return appUsageHistory != null ? appUsageHistory.bucketingReason : 0;
580     }
581 
getElapsedTime(long elapsedRealtime)582     public long getElapsedTime(long elapsedRealtime) {
583         return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
584     }
585 
586     /* Returns the new standby bucket the app is assigned to */
setIdle(String packageName, int userId, boolean idle, long elapsedRealtime)587     public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
588         final int newBucket;
589         final int reason;
590         if (idle) {
591             newBucket = IDLE_BUCKET_CUTOFF;
592             reason = REASON_MAIN_FORCED_BY_USER;
593         } else {
594             newBucket = STANDBY_BUCKET_ACTIVE;
595             // This is to pretend that the app was just used, don't freeze the state anymore.
596             reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION;
597         }
598         setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason, false);
599 
600         return newBucket;
601     }
602 
clearUsage(String packageName, int userId)603     public void clearUsage(String packageName, int userId) {
604         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
605         userHistory.remove(packageName);
606     }
607 
shouldInformListeners(String packageName, int userId, long elapsedRealtime, int bucket)608     boolean shouldInformListeners(String packageName, int userId,
609             long elapsedRealtime, int bucket) {
610         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
611         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
612                 elapsedRealtime, true);
613         if (appUsageHistory.lastInformedBucket != bucket) {
614             appUsageHistory.lastInformedBucket = bucket;
615             return true;
616         }
617         return false;
618     }
619 
620     /**
621      * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds
622      * that corresponds to how long since the app was used.
623      * @param packageName
624      * @param userId
625      * @param elapsedRealtime current time
626      * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0
627      * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0
628      * @return The index whose values the app's used time exceeds (in both arrays) or {@code -1} to
629      *         indicate that the app has never been used.
630      */
getThresholdIndex(String packageName, int userId, long elapsedRealtime, long[] screenTimeThresholds, long[] elapsedTimeThresholds)631     int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
632             long[] screenTimeThresholds, long[] elapsedTimeThresholds) {
633         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
634         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
635                 elapsedRealtime, false);
636         // If we don't have any state for the app, assume never used
637         if (appUsageHistory == null || appUsageHistory.lastUsedElapsedTime < 0
638                 || appUsageHistory.lastUsedScreenTime < 0) {
639             return -1;
640         }
641 
642         long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime;
643         long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime;
644 
645         if (DEBUG) Slog.d(TAG, packageName
646                 + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime
647                 + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime);
648         if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta
649                 + ", elapsed=" + elapsedDelta);
650         for (int i = screenTimeThresholds.length - 1; i >= 0; i--) {
651             if (screenOnDelta >= screenTimeThresholds[i]
652                 && elapsedDelta >= elapsedTimeThresholds[i]) {
653                 return i;
654             }
655         }
656         return 0;
657     }
658 
659     /**
660      * Log a standby bucket change to statsd, and also logcat if debug logging is enabled.
661      */
logAppStandbyBucketChanged(String packageName, int userId, int bucket, int reason)662     private void logAppStandbyBucketChanged(String packageName, int userId, int bucket,
663             int reason) {
664         FrameworkStatsLog.write(
665                 FrameworkStatsLog.APP_STANDBY_BUCKET_CHANGED,
666                 packageName, userId, bucket,
667                 (reason & REASON_MAIN_MASK), (reason & REASON_SUB_MASK));
668         if (DEBUG) {
669             Slog.d(TAG, "Moved " + packageName + " to bucket=" + bucket
670                     + ", reason=0x0" + Integer.toHexString(reason));
671         }
672     }
673 
674     @VisibleForTesting
getBucketExpiryTimeMs(String packageName, int userId, int bucket, long elapsedRealtimeMs)675     long getBucketExpiryTimeMs(String packageName, int userId, int bucket, long elapsedRealtimeMs) {
676         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
677         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
678                 elapsedRealtimeMs, false /* create */);
679         if (appUsageHistory == null || appUsageHistory.bucketExpiryTimesMs == null) {
680             return 0;
681         }
682         return appUsageHistory.bucketExpiryTimesMs.get(bucket, 0);
683     }
684 
685     @VisibleForTesting
getUserFile(int userId)686     File getUserFile(int userId) {
687         return new File(new File(new File(mStorageDir, "users"),
688                 Integer.toString(userId)), APP_IDLE_FILENAME);
689     }
690 
clearLastUsedTimestamps(String packageName, int userId)691     void clearLastUsedTimestamps(String packageName, int userId) {
692         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
693         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
694                 SystemClock.elapsedRealtime(), false /* create */);
695         if (appUsageHistory != null) {
696             appUsageHistory.lastUsedByUserElapsedTime = Integer.MIN_VALUE;
697             appUsageHistory.lastUsedElapsedTime = Integer.MIN_VALUE;
698             appUsageHistory.lastUsedScreenTime = Integer.MIN_VALUE;
699         }
700     }
701 
702     /**
703      * Check if App Idle File exists on disk
704      * @param userId
705      * @return true if file exists
706      */
userFileExists(int userId)707     public boolean userFileExists(int userId) {
708         return getUserFile(userId).exists();
709     }
710 
readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory)711     private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) {
712         FileInputStream fis = null;
713         try {
714             AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
715             fis = appIdleFile.openRead();
716             XmlPullParser parser = Xml.newPullParser();
717             parser.setInput(fis, StandardCharsets.UTF_8.name());
718 
719             int type;
720             while ((type = parser.next()) != XmlPullParser.START_TAG
721                     && type != XmlPullParser.END_DOCUMENT) {
722                 // Skip
723             }
724 
725             if (type != XmlPullParser.START_TAG) {
726                 Slog.e(TAG, "Unable to read app idle file for user " + userId);
727                 return;
728             }
729             if (!parser.getName().equals(TAG_PACKAGES)) {
730                 return;
731             }
732             final int version = getIntValue(parser, ATTR_VERSION, XML_VERSION_INITIAL);
733             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
734                 if (type == XmlPullParser.START_TAG) {
735                     final String name = parser.getName();
736                     if (name.equals(TAG_PACKAGE)) {
737                         final String packageName = parser.getAttributeValue(null, ATTR_NAME);
738                         AppUsageHistory appUsageHistory = new AppUsageHistory();
739                         appUsageHistory.lastUsedElapsedTime =
740                                 Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
741                         appUsageHistory.lastUsedByUserElapsedTime = getLongValue(parser,
742                                 ATTR_LAST_USED_BY_USER_ELAPSED,
743                                 appUsageHistory.lastUsedElapsedTime);
744                         appUsageHistory.lastUsedScreenTime =
745                                 Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
746                         appUsageHistory.lastPredictedTime = getLongValue(parser,
747                                 ATTR_LAST_PREDICTED_TIME, 0L);
748                         String currentBucketString = parser.getAttributeValue(null,
749                                 ATTR_CURRENT_BUCKET);
750                         appUsageHistory.currentBucket = currentBucketString == null
751                                 ? STANDBY_BUCKET_ACTIVE
752                                 : Integer.parseInt(currentBucketString);
753                         String bucketingReason =
754                                 parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
755                         appUsageHistory.lastJobRunTime = getLongValue(parser,
756                                 ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE);
757                         appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT;
758                         if (bucketingReason != null) {
759                             try {
760                                 appUsageHistory.bucketingReason =
761                                         Integer.parseInt(bucketingReason, 16);
762                             } catch (NumberFormatException nfe) {
763                                 Slog.wtf(TAG, "Unable to read bucketing reason", nfe);
764                             }
765                         }
766                         appUsageHistory.lastRestrictAttemptElapsedTime =
767                                 getLongValue(parser, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, 0);
768                         String lastRestrictReason = parser.getAttributeValue(
769                                 null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON);
770                         if (lastRestrictReason != null) {
771                             try {
772                                 appUsageHistory.lastRestrictReason =
773                                         Integer.parseInt(lastRestrictReason, 16);
774                             } catch (NumberFormatException nfe) {
775                                 Slog.wtf(TAG, "Unable to read last restrict reason", nfe);
776                             }
777                         }
778                         appUsageHistory.nextEstimatedLaunchTime = getLongValue(parser,
779                                 ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, 0);
780                         appUsageHistory.lastInformedBucket = -1;
781                         userHistory.put(packageName, appUsageHistory);
782 
783                         if (version >= XML_VERSION_ADD_BUCKET_EXPIRY_TIMES) {
784                             final int outerDepth = parser.getDepth();
785                             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
786                                 if (TAG_BUCKET_EXPIRY_TIMES.equals(parser.getName())) {
787                                     readBucketExpiryTimes(parser, appUsageHistory);
788                                 }
789                             }
790                         } else {
791                             final long bucketActiveTimeoutTime = getLongValue(parser,
792                                     ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L);
793                             final long bucketWorkingSetTimeoutTime = getLongValue(parser,
794                                     ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L);
795                             if (bucketActiveTimeoutTime != 0 || bucketWorkingSetTimeoutTime != 0) {
796                                 insertBucketExpiryTime(appUsageHistory,
797                                         STANDBY_BUCKET_ACTIVE, bucketActiveTimeoutTime);
798                                 insertBucketExpiryTime(appUsageHistory,
799                                         STANDBY_BUCKET_WORKING_SET, bucketWorkingSetTimeoutTime);
800                             }
801                         }
802                     }
803                 }
804             }
805         } catch (IOException | XmlPullParserException e) {
806             Slog.e(TAG, "Unable to read app idle file for user " + userId, e);
807         } finally {
808             IoUtils.closeQuietly(fis);
809         }
810     }
811 
readBucketExpiryTimes(XmlPullParser parser, AppUsageHistory appUsageHistory)812     private void readBucketExpiryTimes(XmlPullParser parser, AppUsageHistory appUsageHistory)
813             throws IOException, XmlPullParserException {
814         final int depth = parser.getDepth();
815         while (XmlUtils.nextElementWithin(parser, depth)) {
816             if (TAG_ITEM.equals(parser.getName())) {
817                 final int bucket = getIntValue(parser, ATTR_BUCKET, STANDBY_BUCKET_UNKNOWN);
818                 if (bucket == STANDBY_BUCKET_UNKNOWN) {
819                     Slog.e(TAG, "Error reading the buckets expiry times");
820                     continue;
821                 }
822                 final long expiryTimeMs = getLongValue(parser, ATTR_EXPIRY_TIME, 0 /* default */);
823                 insertBucketExpiryTime(appUsageHistory, bucket, expiryTimeMs);
824             }
825         }
826     }
827 
insertBucketExpiryTime(AppUsageHistory appUsageHistory, int bucket, long expiryTimeMs)828     private void insertBucketExpiryTime(AppUsageHistory appUsageHistory,
829             int bucket, long expiryTimeMs) {
830         if (expiryTimeMs == 0) {
831             return;
832         }
833         if (appUsageHistory.bucketExpiryTimesMs == null) {
834             appUsageHistory.bucketExpiryTimesMs = new SparseLongArray();
835         }
836         appUsageHistory.bucketExpiryTimesMs.put(bucket, expiryTimeMs);
837     }
838 
getLongValue(XmlPullParser parser, String attrName, long defValue)839     private long getLongValue(XmlPullParser parser, String attrName, long defValue) {
840         String value = parser.getAttributeValue(null, attrName);
841         if (value == null) return defValue;
842         return Long.parseLong(value);
843     }
844 
getIntValue(XmlPullParser parser, String attrName, int defValue)845     private int getIntValue(XmlPullParser parser, String attrName, int defValue) {
846         String value = parser.getAttributeValue(null, attrName);
847         if (value == null) return defValue;
848         return Integer.parseInt(value);
849     }
850 
writeAppIdleTimes(long elapsedRealtimeMs)851     public void writeAppIdleTimes(long elapsedRealtimeMs) {
852         final int size = mIdleHistory.size();
853         for (int i = 0; i < size; i++) {
854             writeAppIdleTimes(mIdleHistory.keyAt(i), elapsedRealtimeMs);
855         }
856     }
857 
writeAppIdleTimes(int userId, long elapsedRealtimeMs)858     public void writeAppIdleTimes(int userId, long elapsedRealtimeMs) {
859         FileOutputStream fos = null;
860         AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
861         try {
862             fos = appIdleFile.startWrite();
863             final BufferedOutputStream bos = new BufferedOutputStream(fos);
864 
865             FastXmlSerializer xml = new FastXmlSerializer();
866             xml.setOutput(bos, StandardCharsets.UTF_8.name());
867             xml.startDocument(null, true);
868             xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
869 
870             xml.startTag(null, TAG_PACKAGES);
871             xml.attribute(null, ATTR_VERSION, String.valueOf(XML_VERSION_CURRENT));
872 
873             final long elapsedTimeMs = getElapsedTime(elapsedRealtimeMs);
874             ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId);
875             final int N = userHistory.size();
876             for (int i = 0; i < N; i++) {
877                 String packageName = userHistory.keyAt(i);
878                 // Skip any unexpected null package names
879                 if (packageName == null) {
880                     Slog.w(TAG, "Skipping App Idle write for unexpected null package");
881                     continue;
882                 }
883                 AppUsageHistory history = userHistory.valueAt(i);
884                 xml.startTag(null, TAG_PACKAGE);
885                 xml.attribute(null, ATTR_NAME, packageName);
886                 xml.attribute(null, ATTR_ELAPSED_IDLE,
887                         Long.toString(history.lastUsedElapsedTime));
888                 xml.attribute(null, ATTR_LAST_USED_BY_USER_ELAPSED,
889                         Long.toString(history.lastUsedByUserElapsedTime));
890                 xml.attribute(null, ATTR_SCREEN_IDLE,
891                         Long.toString(history.lastUsedScreenTime));
892                 xml.attribute(null, ATTR_LAST_PREDICTED_TIME,
893                         Long.toString(history.lastPredictedTime));
894                 xml.attribute(null, ATTR_CURRENT_BUCKET,
895                         Integer.toString(history.currentBucket));
896                 xml.attribute(null, ATTR_BUCKETING_REASON,
897                         Integer.toHexString(history.bucketingReason));
898                 if (history.lastJobRunTime != Long.MIN_VALUE) {
899                     xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history
900                             .lastJobRunTime));
901                 }
902                 if (history.lastRestrictAttemptElapsedTime > 0) {
903                     xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED,
904                             Long.toString(history.lastRestrictAttemptElapsedTime));
905                 }
906                 xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON,
907                         Integer.toHexString(history.lastRestrictReason));
908                 if (history.nextEstimatedLaunchTime > 0) {
909                     xml.attribute(null, ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME,
910                             Long.toString(history.nextEstimatedLaunchTime));
911                 }
912                 if (history.bucketExpiryTimesMs != null) {
913                     xml.startTag(null, TAG_BUCKET_EXPIRY_TIMES);
914                     final int size = history.bucketExpiryTimesMs.size();
915                     for (int j = 0; j < size; ++j) {
916                         final long expiryTimeMs = history.bucketExpiryTimesMs.valueAt(j);
917                         // Skip writing to disk if the expiry time already elapsed.
918                         if (expiryTimeMs < elapsedTimeMs) {
919                             continue;
920                         }
921                         final int bucket = history.bucketExpiryTimesMs.keyAt(j);
922                         xml.startTag(null, TAG_ITEM);
923                         xml.attribute(null, ATTR_BUCKET, String.valueOf(bucket));
924                         xml.attribute(null, ATTR_EXPIRY_TIME, String.valueOf(expiryTimeMs));
925                         xml.endTag(null, TAG_ITEM);
926                     }
927                     xml.endTag(null, TAG_BUCKET_EXPIRY_TIMES);
928                 }
929                 xml.endTag(null, TAG_PACKAGE);
930             }
931 
932             xml.endTag(null, TAG_PACKAGES);
933             xml.endDocument();
934             appIdleFile.finishWrite(fos);
935         } catch (Exception e) {
936             appIdleFile.failWrite(fos);
937             Slog.e(TAG, "Error writing app idle file for user " + userId, e);
938         }
939     }
940 
dumpUsers(IndentingPrintWriter idpw, int[] userIds, List<String> pkgs)941     public void dumpUsers(IndentingPrintWriter idpw, int[] userIds, List<String> pkgs) {
942         final int numUsers = userIds.length;
943         for (int i = 0; i < numUsers; i++) {
944             idpw.println();
945             dumpUser(idpw, userIds[i], pkgs);
946         }
947     }
948 
dumpUser(IndentingPrintWriter idpw, int userId, List<String> pkgs)949     private void dumpUser(IndentingPrintWriter idpw, int userId, List<String> pkgs) {
950         idpw.print("User ");
951         idpw.print(userId);
952         idpw.println(" App Standby States:");
953         idpw.increaseIndent();
954         ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
955         final long now = System.currentTimeMillis();
956         final long elapsedRealtime = SystemClock.elapsedRealtime();
957         final long totalElapsedTime = getElapsedTime(elapsedRealtime);
958         final long screenOnTime = getScreenOnTime(elapsedRealtime);
959         if (userHistory == null) return;
960         final int P = userHistory.size();
961         for (int p = 0; p < P; p++) {
962             final String packageName = userHistory.keyAt(p);
963             final AppUsageHistory appUsageHistory = userHistory.valueAt(p);
964             if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(packageName)) {
965                 continue;
966             }
967             idpw.print("package=" + packageName);
968             idpw.print(" u=" + userId);
969             idpw.print(" bucket=" + appUsageHistory.currentBucket
970                     + " reason="
971                     + UsageStatsManager.reasonToString(appUsageHistory.bucketingReason));
972             idpw.print(" used=");
973             printLastActionElapsedTime(idpw, totalElapsedTime, appUsageHistory.lastUsedElapsedTime);
974             idpw.print(" usedByUser=");
975             printLastActionElapsedTime(idpw, totalElapsedTime,
976                     appUsageHistory.lastUsedByUserElapsedTime);
977             idpw.print(" usedScr=");
978             printLastActionElapsedTime(idpw, totalElapsedTime, appUsageHistory.lastUsedScreenTime);
979             idpw.print(" lastPred=");
980             printLastActionElapsedTime(idpw, totalElapsedTime, appUsageHistory.lastPredictedTime);
981             dumpBucketExpiryTimes(idpw, appUsageHistory, totalElapsedTime);
982             idpw.print(" lastJob=");
983             TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
984             idpw.print(" lastInformedBucket=" + appUsageHistory.lastInformedBucket);
985             if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) {
986                 idpw.print(" lastRestrictAttempt=");
987                 TimeUtils.formatDuration(
988                         totalElapsedTime - appUsageHistory.lastRestrictAttemptElapsedTime, idpw);
989                 idpw.print(" lastRestrictReason="
990                         + UsageStatsManager.reasonToString(appUsageHistory.lastRestrictReason));
991             }
992             if (appUsageHistory.nextEstimatedLaunchTime > 0) {
993                 idpw.print(" nextEstimatedLaunchTime=");
994                 TimeUtils.formatDuration(appUsageHistory.nextEstimatedLaunchTime - now, idpw);
995             }
996             idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
997             idpw.println();
998         }
999         idpw.println();
1000         idpw.print("totalElapsedTime=");
1001         TimeUtils.formatDuration(getElapsedTime(elapsedRealtime), idpw);
1002         idpw.println();
1003         idpw.print("totalScreenOnTime=");
1004         TimeUtils.formatDuration(getScreenOnTime(elapsedRealtime), idpw);
1005         idpw.println();
1006         idpw.decreaseIndent();
1007     }
1008 
printLastActionElapsedTime(IndentingPrintWriter idpw, long totalElapsedTimeMS, long lastActionTimeMs)1009     private void printLastActionElapsedTime(IndentingPrintWriter idpw, long totalElapsedTimeMS,
1010             long lastActionTimeMs) {
1011         if (lastActionTimeMs < 0) {
1012             idpw.print("<uninitialized>");
1013         } else {
1014             TimeUtils.formatDuration(totalElapsedTimeMS - lastActionTimeMs, idpw);
1015         }
1016     }
1017 
dumpBucketExpiryTimes(IndentingPrintWriter idpw, AppUsageHistory appUsageHistory, long totalElapsedTimeMs)1018     private void dumpBucketExpiryTimes(IndentingPrintWriter idpw, AppUsageHistory appUsageHistory,
1019             long totalElapsedTimeMs) {
1020         idpw.print(" expiryTimes=");
1021         if (appUsageHistory.bucketExpiryTimesMs == null
1022                 || appUsageHistory.bucketExpiryTimesMs.size() == 0) {
1023             idpw.print("<none>");
1024             return;
1025         }
1026         idpw.print("(");
1027         final int size = appUsageHistory.bucketExpiryTimesMs.size();
1028         for (int i = 0; i < size; ++i) {
1029             final int bucket = appUsageHistory.bucketExpiryTimesMs.keyAt(i);
1030             final long expiryTimeMs = appUsageHistory.bucketExpiryTimesMs.valueAt(i);
1031             if (i != 0) {
1032                 idpw.print(",");
1033             }
1034             idpw.print(bucket + ":");
1035             TimeUtils.formatDuration(totalElapsedTimeMs - expiryTimeMs, idpw);
1036         }
1037         idpw.print(")");
1038     }
1039 }
1040