blob: 46d449a9257c3d785b5fec1df52a30757665c4d6 [file] [log] [blame]
Amith Yamasani0a11e692015-05-08 16:36:21 -07001/**
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
17package com.android.server.usage;
18
Amith Yamasani119be9a2018-02-18 22:23:00 -080019import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
Kweku Adamsc182d5e2020-01-08 18:37:26 -080020import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
Amith Yamasani119be9a2018-02-18 22:23:00 -080021import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
22import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
23import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
Kweku Adams12752132020-02-18 15:36:48 -080024import static android.app.usage.UsageStatsManager.REASON_SUB_MASK;
Amith Yamasani119be9a2018-02-18 22:23:00 -080025import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
Amith Yamasani172612c2017-12-15 10:51:53 -080026import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
27import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
28import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
Kweku Adamsc6a9b342020-01-08 18:37:26 -080029import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
Amith Yamasanibbbad9c2018-02-10 16:46:38 -080030import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
Amith Yamasani172612c2017-12-15 10:51:53 -080031
Kweku Adamsc6a9b342020-01-08 18:37:26 -080032import static com.android.server.usage.AppStandbyController.isUserUsage;
33
Suprabh Shukla868bde22018-02-20 20:59:52 -080034import android.app.usage.AppStandbyInfo;
Amith Yamasaniafbccb72017-11-27 10:44:24 -080035import android.app.usage.UsageStatsManager;
Amith Yamasania93542f2016-02-03 18:02:06 -080036import android.os.SystemClock;
Amith Yamasani0a11e692015-05-08 16:36:21 -070037import android.util.ArrayMap;
Amith Yamasania93542f2016-02-03 18:02:06 -080038import android.util.AtomicFile;
39import android.util.Slog;
Amith Yamasani0a11e692015-05-08 16:36:21 -070040import android.util.SparseArray;
Amith Yamasania93542f2016-02-03 18:02:06 -080041import android.util.TimeUtils;
42import android.util.Xml;
Amith Yamasani0a11e692015-05-08 16:36:21 -070043
Amith Yamasania93542f2016-02-03 18:02:06 -080044import com.android.internal.annotations.VisibleForTesting;
Sudheer Shankab497dd42020-02-16 22:23:24 -080045import com.android.internal.util.CollectionUtils;
Amith Yamasania93542f2016-02-03 18:02:06 -080046import com.android.internal.util.FastXmlSerializer;
Kweku Adams12752132020-02-18 15:36:48 -080047import com.android.internal.util.FrameworkStatsLog;
Amith Yamasani0a11e692015-05-08 16:36:21 -070048import com.android.internal.util.IndentingPrintWriter;
49
Amith Yamasania93542f2016-02-03 18:02:06 -080050import libcore.io.IoUtils;
51
52import org.xmlpull.v1.XmlPullParser;
53import org.xmlpull.v1.XmlPullParserException;
54
55import java.io.BufferedOutputStream;
56import java.io.BufferedReader;
57import java.io.File;
58import java.io.FileInputStream;
59import java.io.FileOutputStream;
60import java.io.FileReader;
61import java.io.IOException;
62import java.nio.charset.StandardCharsets;
Suprabh Shukla868bde22018-02-20 20:59:52 -080063import java.util.ArrayList;
Sudheer Shankab497dd42020-02-16 22:23:24 -080064import java.util.List;
Amith Yamasania93542f2016-02-03 18:02:06 -080065
Amith Yamasani0a11e692015-05-08 16:36:21 -070066/**
67 * Keeps track of recent active state changes in apps.
68 * Access should be guarded by a lock by the caller.
69 */
70public class AppIdleHistory {
71
Amith Yamasania93542f2016-02-03 18:02:06 -080072 private static final String TAG = "AppIdleHistory";
73
Amith Yamasani17fffee2017-09-29 13:17:43 -070074 private static final boolean DEBUG = AppStandbyController.DEBUG;
75
Amith Yamasania93542f2016-02-03 18:02:06 -080076 // History for all users and all packages
Amith Yamasani17fffee2017-09-29 13:17:43 -070077 private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
Amith Yamasani0a11e692015-05-08 16:36:21 -070078 private static final long ONE_MINUTE = 60 * 1000;
Amith Yamasani0a11e692015-05-08 16:36:21 -070079
Amith Yamasani3154dcf2018-03-27 18:24:04 -070080 private static final int STANDBY_BUCKET_UNKNOWN = -1;
81
Amith Yamasania93542f2016-02-03 18:02:06 -080082 @VisibleForTesting
83 static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
84 private static final String TAG_PACKAGES = "packages";
85 private static final String TAG_PACKAGE = "package";
86 private static final String ATTR_NAME = "name";
87 // Screen on timebase time when app was last used
88 private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
89 // Elapsed timebase time when app was last used
90 private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime";
Kweku Adamsc6a9b342020-01-08 18:37:26 -080091 // Elapsed timebase time when app was last used by the user
92 private static final String ATTR_LAST_USED_BY_USER_ELAPSED = "lastUsedByUserElapsedTime";
Amith Yamasanibd7b3022017-12-06 17:40:25 -080093 // Elapsed timebase time when the app bucket was last predicted externally
94 private static final String ATTR_LAST_PREDICTED_TIME = "lastPredictedTime";
95 // The standby bucket for the app
Amith Yamasani17fffee2017-09-29 13:17:43 -070096 private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
Amith Yamasanibd7b3022017-12-06 17:40:25 -080097 // The reason the app was put in the above bucket
Amith Yamasani17fffee2017-09-29 13:17:43 -070098 private static final String ATTR_BUCKETING_REASON = "bucketReason";
Amith Yamasani53f06ea2018-01-05 17:53:46 -080099 // The last time a job was run for this app
100 private static final String ATTR_LAST_RUN_JOB_TIME = "lastJobRunTime";
101 // The time when the forced active state can be overridden.
Amith Yamasanibbbad9c2018-02-10 16:46:38 -0800102 private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime";
103 // The time when the forced working_set state can be overridden.
104 private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime";
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800105 // Elapsed timebase time when the app was last marked for restriction.
106 private static final String ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED =
107 "lastRestrictionAttemptElapsedTime";
108 // Reason why the app was last marked for restriction.
109 private static final String ATTR_LAST_RESTRICTION_ATTEMPT_REASON =
110 "lastRestrictionAttemptReason";
Amith Yamasani17fffee2017-09-29 13:17:43 -0700111
Amith Yamasania93542f2016-02-03 18:02:06 -0800112 // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
113 private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
114 private long mElapsedDuration; // Total device on duration since device was "born"
115
116 // screen on time = mScreenOnDuration + (timeNow - mScreenOnSnapshot)
117 private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration
118 private long mScreenOnDuration; // Total screen on duration since device was "born"
119
Amith Yamasania93542f2016-02-03 18:02:06 -0800120 private final File mStorageDir;
121
122 private boolean mScreenOn;
123
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800124 static class AppUsageHistory {
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800125 // Last used time (including system usage), using elapsed timebase
Amith Yamasania93542f2016-02-03 18:02:06 -0800126 long lastUsedElapsedTime;
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800127 // Last time the user used the app, using elapsed timebase
128 long lastUsedByUserElapsedTime;
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800129 // Last used time using screen_on timebase
Amith Yamasania93542f2016-02-03 18:02:06 -0800130 long lastUsedScreenTime;
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800131 // Last predicted time using elapsed timebase
132 long lastPredictedTime;
Amith Yamasani3154dcf2018-03-27 18:24:04 -0700133 // Last predicted bucket
134 @UsageStatsManager.StandbyBuckets
135 int lastPredictedBucket = STANDBY_BUCKET_UNKNOWN;
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800136 // Standby bucket
Amith Yamasaniafbccb72017-11-27 10:44:24 -0800137 @UsageStatsManager.StandbyBuckets
138 int currentBucket;
Amith Yamasani119be9a2018-02-18 22:23:00 -0800139 // Reason for setting the standby bucket. The value here is a combination of
140 // one of UsageStatsManager.REASON_MAIN_* and one (or none) of
141 // UsageStatsManager.REASON_SUB_*. Also see REASON_MAIN_MASK and REASON_SUB_MASK.
142 int bucketingReason;
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800143 // In-memory only, last bucket for which the listeners were informed
Amith Yamasani84cd7b72017-11-07 13:59:37 -0800144 int lastInformedBucket;
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800145 // The last time a job was run for this app, using elapsed timebase
146 long lastJobRunTime;
Amith Yamasanibbbad9c2018-02-10 16:46:38 -0800147 // When should the bucket active state timeout, in elapsed timebase, if greater than
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800148 // lastUsedElapsedTime.
149 // This is used to keep the app in a high bucket regardless of other timeouts and
150 // predictions.
Amith Yamasanibbbad9c2018-02-10 16:46:38 -0800151 long bucketActiveTimeoutTime;
152 // If there's a forced working_set state, this is when it times out. This can be sitting
153 // under any active state timeout, so that it becomes applicable after the active state
154 // timeout expires.
155 long bucketWorkingSetTimeoutTime;
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800156 // The last time an agent attempted to put the app into the RESTRICTED bucket.
157 long lastRestrictAttemptElapsedTime;
158 // The last reason the app was marked to be put into the RESTRICTED bucket.
159 int lastRestrictReason;
Amith Yamasania93542f2016-02-03 18:02:06 -0800160 }
161
Amith Yamasania93542f2016-02-03 18:02:06 -0800162 AppIdleHistory(File storageDir, long elapsedRealtime) {
163 mElapsedSnapshot = elapsedRealtime;
164 mScreenOnSnapshot = elapsedRealtime;
165 mStorageDir = storageDir;
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800166 readScreenOnTime();
Amith Yamasania93542f2016-02-03 18:02:06 -0800167 }
168
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800169 public void updateDisplay(boolean screenOn, long elapsedRealtime) {
Amith Yamasania93542f2016-02-03 18:02:06 -0800170 if (screenOn == mScreenOn) return;
171
172 mScreenOn = screenOn;
173 if (mScreenOn) {
174 mScreenOnSnapshot = elapsedRealtime;
175 } else {
176 mScreenOnDuration += elapsedRealtime - mScreenOnSnapshot;
177 mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
Amith Yamasania93542f2016-02-03 18:02:06 -0800178 mElapsedSnapshot = elapsedRealtime;
179 }
Amith Yamasani17fffee2017-09-29 13:17:43 -0700180 if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot
181 + ", mScreenOnDuration=" + mScreenOnDuration
182 + ", mScreenOn=" + mScreenOn);
Amith Yamasania93542f2016-02-03 18:02:06 -0800183 }
184
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800185 public long getScreenOnTime(long elapsedRealtime) {
Amith Yamasania93542f2016-02-03 18:02:06 -0800186 long screenOnTime = mScreenOnDuration;
187 if (mScreenOn) {
188 screenOnTime += elapsedRealtime - mScreenOnSnapshot;
189 }
190 return screenOnTime;
191 }
192
193 @VisibleForTesting
194 File getScreenOnTimeFile() {
195 return new File(mStorageDir, "screen_on_time");
196 }
197
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800198 private void readScreenOnTime() {
Amith Yamasania93542f2016-02-03 18:02:06 -0800199 File screenOnTimeFile = getScreenOnTimeFile();
200 if (screenOnTimeFile.exists()) {
201 try {
202 BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile));
203 mScreenOnDuration = Long.parseLong(reader.readLine());
204 mElapsedDuration = Long.parseLong(reader.readLine());
205 reader.close();
206 } catch (IOException | NumberFormatException e) {
207 }
208 } else {
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800209 writeScreenOnTime();
Amith Yamasania93542f2016-02-03 18:02:06 -0800210 }
211 }
212
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800213 private void writeScreenOnTime() {
Amith Yamasania93542f2016-02-03 18:02:06 -0800214 AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile());
215 FileOutputStream fos = null;
216 try {
217 fos = screenOnTimeFile.startWrite();
218 fos.write((Long.toString(mScreenOnDuration) + "\n"
219 + Long.toString(mElapsedDuration) + "\n").getBytes());
220 screenOnTimeFile.finishWrite(fos);
221 } catch (IOException ioe) {
222 screenOnTimeFile.failWrite(fos);
223 }
224 }
225
226 /**
227 * To be called periodically to keep track of elapsed time when app idle times are written
228 */
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800229 public void writeAppIdleDurations() {
Amith Yamasania93542f2016-02-03 18:02:06 -0800230 final long elapsedRealtime = SystemClock.elapsedRealtime();
231 // Only bump up and snapshot the elapsed time. Don't change screen on duration.
232 mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
233 mElapsedSnapshot = elapsedRealtime;
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800234 writeScreenOnTime();
Amith Yamasania93542f2016-02-03 18:02:06 -0800235 }
236
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800237 /**
238 * Mark the app as used and update the bucket if necessary. If there is a timeout specified
239 * that's in the future, then the usage event is temporary and keeps the app in the specified
240 * bucket at least until the timeout is reached. This can be used to keep the app in an
241 * elevated bucket for a while until some important task gets to run.
Christopher Tated117b292018-01-05 17:32:36 -0800242 * @param appUsageHistory the usage record for the app being updated
243 * @param packageName name of the app being updated, for logging purposes
244 * @param newBucket the bucket to set the app to
Amith Yamasani119be9a2018-02-18 22:23:00 -0800245 * @param usageReason the sub-reason for usage, one of REASON_SUB_USAGE_*
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800246 * @param elapsedRealtime mark as used time if non-zero
Amith Yamasanibbbad9c2018-02-10 16:46:38 -0800247 * @param timeout set the timeout of the specified bucket, if non-zero. Can only be used
248 * with bucket values of ACTIVE and WORKING_SET.
Kweku Adams12752132020-02-18 15:36:48 -0800249 * @return {@code appUsageHistory}
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800250 */
Kweku Adams12752132020-02-18 15:36:48 -0800251 AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int userId,
Amith Yamasani119be9a2018-02-18 22:23:00 -0800252 int newBucket, int usageReason, long elapsedRealtime, long timeout) {
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800253 int bucketingReason = REASON_MAIN_USAGE | usageReason;
254 final boolean isUserUsage = isUserUsage(bucketingReason);
255
256 if (appUsageHistory.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage) {
257 // Only user usage should bring an app out of the RESTRICTED bucket.
258 newBucket = STANDBY_BUCKET_RESTRICTED;
259 bucketingReason = appUsageHistory.bucketingReason;
260 } else {
261 // Set the timeout if applicable
262 if (timeout > elapsedRealtime) {
263 // Convert to elapsed timebase
264 final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot);
265 if (newBucket == STANDBY_BUCKET_ACTIVE) {
266 appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime,
267 appUsageHistory.bucketActiveTimeoutTime);
268 } else if (newBucket == STANDBY_BUCKET_WORKING_SET) {
269 appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime,
270 appUsageHistory.bucketWorkingSetTimeoutTime);
271 } else {
272 throw new IllegalArgumentException("Cannot set a timeout on bucket="
273 + newBucket);
274 }
Amith Yamasanibbbad9c2018-02-10 16:46:38 -0800275 }
276 }
277
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800278 if (elapsedRealtime != 0) {
279 appUsageHistory.lastUsedElapsedTime = mElapsedDuration
280 + (elapsedRealtime - mElapsedSnapshot);
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800281 if (isUserUsage) {
282 appUsageHistory.lastUsedByUserElapsedTime = appUsageHistory.lastUsedElapsedTime;
283 }
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800284 appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
285 }
Amith Yamasania93542f2016-02-03 18:02:06 -0800286
Christopher Tated117b292018-01-05 17:32:36 -0800287 if (appUsageHistory.currentBucket > newBucket) {
288 appUsageHistory.currentBucket = newBucket;
Kweku Adams12752132020-02-18 15:36:48 -0800289 logAppStandbyBucketChanged(packageName, userId, newBucket, bucketingReason);
Amith Yamasani17fffee2017-09-29 13:17:43 -0700290 }
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800291 appUsageHistory.bucketingReason = bucketingReason;
Amith Yamasani803eab692017-11-09 17:47:04 -0800292
Christopher Tated117b292018-01-05 17:32:36 -0800293 return appUsageHistory;
294 }
295
296 /**
297 * Mark the app as used and update the bucket if necessary. If there is a timeout specified
298 * that's in the future, then the usage event is temporary and keeps the app in the specified
299 * bucket at least until the timeout is reached. This can be used to keep the app in an
300 * elevated bucket for a while until some important task gets to run.
301 * @param packageName
302 * @param userId
303 * @param newBucket the bucket to set the app to
Amith Yamasani119be9a2018-02-18 22:23:00 -0800304 * @param usageReason sub reason for usage
305 * @param nowElapsed mark as used time if non-zero
Amith Yamasanibbbad9c2018-02-10 16:46:38 -0800306 * @param timeout set the timeout of the specified bucket, if non-zero. Can only be used
307 * with bucket values of ACTIVE and WORKING_SET.
Christopher Tated117b292018-01-05 17:32:36 -0800308 * @return
309 */
310 public AppUsageHistory reportUsage(String packageName, int userId, int newBucket,
Amith Yamasani119be9a2018-02-18 22:23:00 -0800311 int usageReason, long nowElapsed, long timeout) {
Christopher Tated117b292018-01-05 17:32:36 -0800312 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
313 AppUsageHistory history = getPackageHistory(userHistory, packageName, nowElapsed, true);
Kweku Adams12752132020-02-18 15:36:48 -0800314 return reportUsage(history, packageName, userId, newBucket, usageReason, nowElapsed,
315 timeout);
Amith Yamasani803eab692017-11-09 17:47:04 -0800316 }
317
Amith Yamasani17fffee2017-09-29 13:17:43 -0700318 private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
319 ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
Amith Yamasani67768492015-06-09 12:23:58 -0700320 if (userHistory == null) {
321 userHistory = new ArrayMap<>();
322 mIdleHistory.put(userId, userHistory);
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800323 readAppIdleTimes(userId, userHistory);
Amith Yamasani67768492015-06-09 12:23:58 -0700324 }
325 return userHistory;
326 }
327
Amith Yamasani17fffee2017-09-29 13:17:43 -0700328 private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory,
329 String packageName, long elapsedRealtime, boolean create) {
330 AppUsageHistory appUsageHistory = userHistory.get(packageName);
331 if (appUsageHistory == null && create) {
332 appUsageHistory = new AppUsageHistory();
333 appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
334 appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800335 appUsageHistory.lastPredictedTime = getElapsedTime(0);
Amith Yamasani172612c2017-12-15 10:51:53 -0800336 appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER;
Amith Yamasani119be9a2018-02-18 22:23:00 -0800337 appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT;
Amith Yamasaniafbccb72017-11-27 10:44:24 -0800338 appUsageHistory.lastInformedBucket = -1;
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800339 appUsageHistory.lastJobRunTime = Long.MIN_VALUE; // long long time ago
Amith Yamasani17fffee2017-09-29 13:17:43 -0700340 userHistory.put(packageName, appUsageHistory);
Amith Yamasani67768492015-06-09 12:23:58 -0700341 }
Amith Yamasani17fffee2017-09-29 13:17:43 -0700342 return appUsageHistory;
Amith Yamasani67768492015-06-09 12:23:58 -0700343 }
344
Amith Yamasania93542f2016-02-03 18:02:06 -0800345 public void onUserRemoved(int userId) {
Amith Yamasani67768492015-06-09 12:23:58 -0700346 mIdleHistory.remove(userId);
347 }
348
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800349 public boolean isIdle(String packageName, int userId, long elapsedRealtime) {
Amith Yamasani17fffee2017-09-29 13:17:43 -0700350 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
351 AppUsageHistory appUsageHistory =
352 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
Varun Shah7609b752018-10-15 15:07:47 -0700353 return appUsageHistory.currentBucket >= STANDBY_BUCKET_RARE;
Amith Yamasania93542f2016-02-03 18:02:06 -0800354 }
355
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800356 public AppUsageHistory getAppUsageHistory(String packageName, int userId,
357 long elapsedRealtime) {
358 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
359 AppUsageHistory appUsageHistory =
360 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
361 return appUsageHistory;
362 }
363
Amith Yamasani17fffee2017-09-29 13:17:43 -0700364 public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
Amith Yamasani119be9a2018-02-18 22:23:00 -0800365 int bucket, int reason) {
Makoto Onukia0058b42018-05-22 16:32:23 -0700366 setAppStandbyBucket(packageName, userId, elapsedRealtime, bucket, reason, false);
367 }
368
369 public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
370 int bucket, int reason, boolean resetTimeout) {
Amith Yamasani17fffee2017-09-29 13:17:43 -0700371 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
372 AppUsageHistory appUsageHistory =
373 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
Kweku Adams12752132020-02-18 15:36:48 -0800374 final boolean changed = appUsageHistory.currentBucket != bucket;
Amith Yamasani17fffee2017-09-29 13:17:43 -0700375 appUsageHistory.currentBucket = bucket;
376 appUsageHistory.bucketingReason = reason;
Makoto Onukia0058b42018-05-22 16:32:23 -0700377
378 final long elapsed = getElapsedTime(elapsedRealtime);
379
Amith Yamasani119be9a2018-02-18 22:23:00 -0800380 if ((reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED) {
Makoto Onukia0058b42018-05-22 16:32:23 -0700381 appUsageHistory.lastPredictedTime = elapsed;
Amith Yamasani3154dcf2018-03-27 18:24:04 -0700382 appUsageHistory.lastPredictedBucket = bucket;
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800383 }
Makoto Onukia0058b42018-05-22 16:32:23 -0700384 if (resetTimeout) {
385 appUsageHistory.bucketActiveTimeoutTime = elapsed;
386 appUsageHistory.bucketWorkingSetTimeoutTime = elapsed;
387 }
Kweku Adams12752132020-02-18 15:36:48 -0800388 if (changed) {
389 logAppStandbyBucketChanged(packageName, userId, bucket, reason);
Amith Yamasani17fffee2017-09-29 13:17:43 -0700390 }
391 }
392
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800393 /**
Amith Yamasani3154dcf2018-03-27 18:24:04 -0700394 * Update the prediction for the app but don't change the actual bucket
395 * @param app The app for which the prediction was made
396 * @param elapsedTimeAdjusted The elapsed time in the elapsed duration timebase
397 * @param bucket The predicted bucket
398 */
399 public void updateLastPrediction(AppUsageHistory app, long elapsedTimeAdjusted, int bucket) {
400 app.lastPredictedTime = elapsedTimeAdjusted;
401 app.lastPredictedBucket = bucket;
402 }
403
404 /**
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800405 * Marks the last time a job was run, with the given elapsedRealtime. The time stored is
406 * based on the elapsed timebase.
407 * @param packageName
408 * @param userId
409 * @param elapsedRealtime
410 */
411 public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
412 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
413 AppUsageHistory appUsageHistory =
414 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
415 appUsageHistory.lastJobRunTime = getElapsedTime(elapsedRealtime);
416 }
417
418 /**
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800419 * Notes an attempt to put the app in the {@link UsageStatsManager#STANDBY_BUCKET_RESTRICTED}
420 * bucket.
421 *
422 * @param packageName The package name of the app that is being restricted
423 * @param userId The ID of the user in which the app is being restricted
424 * @param elapsedRealtime The time the attempt was made, in the (unadjusted) elapsed realtime
425 * timebase
426 * @param reason The reason for the restriction attempt
427 */
428 void noteRestrictionAttempt(String packageName, int userId, long elapsedRealtime, int reason) {
429 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
430 AppUsageHistory appUsageHistory =
431 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
432 appUsageHistory.lastRestrictAttemptElapsedTime = getElapsedTime(elapsedRealtime);
433 appUsageHistory.lastRestrictReason = reason;
434 }
435
436 /**
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800437 * Returns the time since the last job was run for this app. This can be larger than the
438 * current elapsedRealtime, in case it happened before boot or a really large value if no jobs
439 * were ever run.
440 * @param packageName
441 * @param userId
442 * @param elapsedRealtime
443 * @return
444 */
445 public long getTimeSinceLastJobRun(String packageName, int userId, long elapsedRealtime) {
446 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
447 AppUsageHistory appUsageHistory =
Varun Shah7609b752018-10-15 15:07:47 -0700448 getPackageHistory(userHistory, packageName, elapsedRealtime, false);
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800449 // Don't adjust the default, else it'll wrap around to a positive value
Varun Shah7609b752018-10-15 15:07:47 -0700450 if (appUsageHistory == null || appUsageHistory.lastJobRunTime == Long.MIN_VALUE) {
451 return Long.MAX_VALUE;
452 }
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800453 return getElapsedTime(elapsedRealtime) - appUsageHistory.lastJobRunTime;
454 }
455
Amith Yamasani17fffee2017-09-29 13:17:43 -0700456 public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
457 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
458 AppUsageHistory appUsageHistory =
Varun Shah7609b752018-10-15 15:07:47 -0700459 getPackageHistory(userHistory, packageName, elapsedRealtime, false);
460 return appUsageHistory == null ? STANDBY_BUCKET_NEVER : appUsageHistory.currentBucket;
Amith Yamasani17fffee2017-09-29 13:17:43 -0700461 }
462
Suprabh Shukla868bde22018-02-20 20:59:52 -0800463 public ArrayList<AppStandbyInfo> getAppStandbyBuckets(int userId, boolean appIdleEnabled) {
Amith Yamasanie8789312017-12-10 14:34:26 -0800464 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
465 int size = userHistory.size();
Suprabh Shukla868bde22018-02-20 20:59:52 -0800466 ArrayList<AppStandbyInfo> buckets = new ArrayList<>(size);
Amith Yamasanie8789312017-12-10 14:34:26 -0800467 for (int i = 0; i < size; i++) {
Suprabh Shukla868bde22018-02-20 20:59:52 -0800468 buckets.add(new AppStandbyInfo(userHistory.keyAt(i),
469 appIdleEnabled ? userHistory.valueAt(i).currentBucket : STANDBY_BUCKET_ACTIVE));
Amith Yamasanie8789312017-12-10 14:34:26 -0800470 }
471 return buckets;
472 }
473
Amith Yamasani119be9a2018-02-18 22:23:00 -0800474 public int getAppStandbyReason(String packageName, int userId, long elapsedRealtime) {
Amith Yamasani17fffee2017-09-29 13:17:43 -0700475 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
476 AppUsageHistory appUsageHistory =
477 getPackageHistory(userHistory, packageName, elapsedRealtime, false);
Amith Yamasani119be9a2018-02-18 22:23:00 -0800478 return appUsageHistory != null ? appUsageHistory.bucketingReason : 0;
Amith Yamasani17fffee2017-09-29 13:17:43 -0700479 }
480
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800481 public long getElapsedTime(long elapsedRealtime) {
Amith Yamasania93542f2016-02-03 18:02:06 -0800482 return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
483 }
484
Christopher Tatea732f012017-10-26 17:26:53 -0700485 /* Returns the new standby bucket the app is assigned to */
486 public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
Kweku Adams12752132020-02-18 15:36:48 -0800487 final int newBucket;
488 final int reason;
Amith Yamasani17fffee2017-09-29 13:17:43 -0700489 if (idle) {
Kweku Adams12752132020-02-18 15:36:48 -0800490 newBucket = STANDBY_BUCKET_RARE;
491 reason = REASON_MAIN_FORCED_BY_USER;
Amith Yamasani17fffee2017-09-29 13:17:43 -0700492 } else {
Kweku Adams12752132020-02-18 15:36:48 -0800493 newBucket = STANDBY_BUCKET_ACTIVE;
Amith Yamasani17fffee2017-09-29 13:17:43 -0700494 // This is to pretend that the app was just used, don't freeze the state anymore.
Kweku Adams12752132020-02-18 15:36:48 -0800495 reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION;
Amith Yamasani17fffee2017-09-29 13:17:43 -0700496 }
Kweku Adams12752132020-02-18 15:36:48 -0800497 setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason, false);
498
499 return newBucket;
Amith Yamasania93542f2016-02-03 18:02:06 -0800500 }
501
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800502 public void clearUsage(String packageName, int userId) {
Amith Yamasani17fffee2017-09-29 13:17:43 -0700503 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
Amith Yamasanibdda1e02016-03-14 11:55:38 -0700504 userHistory.remove(packageName);
505 }
506
Amith Yamasani17fffee2017-09-29 13:17:43 -0700507 boolean shouldInformListeners(String packageName, int userId,
Amith Yamasani84cd7b72017-11-07 13:59:37 -0800508 long elapsedRealtime, int bucket) {
Amith Yamasani17fffee2017-09-29 13:17:43 -0700509 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
510 AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
511 elapsedRealtime, true);
Amith Yamasani84cd7b72017-11-07 13:59:37 -0800512 if (appUsageHistory.lastInformedBucket != bucket) {
513 appUsageHistory.lastInformedBucket = bucket;
Amith Yamasani17fffee2017-09-29 13:17:43 -0700514 return true;
515 }
516 return false;
Amith Yamasania93542f2016-02-03 18:02:06 -0800517 }
518
Amith Yamasani17fffee2017-09-29 13:17:43 -0700519 /**
520 * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds
521 * that corresponds to how long since the app was used.
522 * @param packageName
523 * @param userId
524 * @param elapsedRealtime current time
525 * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0
526 * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0
527 * @return The index whose values the app's used time exceeds (in both arrays)
528 */
529 int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
530 long[] screenTimeThresholds, long[] elapsedTimeThresholds) {
531 ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
532 AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
533 elapsedRealtime, false);
534 // If we don't have any state for the app, assume never used
535 if (appUsageHistory == null) return screenTimeThresholds.length - 1;
536
537 long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime;
538 long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime;
539
540 if (DEBUG) Slog.d(TAG, packageName
541 + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime
542 + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime);
543 if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta
544 + ", elapsed=" + elapsedDelta);
545 for (int i = screenTimeThresholds.length - 1; i >= 0; i--) {
546 if (screenOnDelta >= screenTimeThresholds[i]
547 && elapsedDelta >= elapsedTimeThresholds[i]) {
548 return i;
549 }
550 }
551 return 0;
552 }
553
Kweku Adams12752132020-02-18 15:36:48 -0800554 /**
555 * Log a standby bucket change to statsd, and also logcat if debug logging is enabled.
556 */
557 private void logAppStandbyBucketChanged(String packageName, int userId, int bucket,
558 int reason) {
559 FrameworkStatsLog.write(
560 FrameworkStatsLog.APP_STANDBY_BUCKET_CHANGED,
561 packageName, userId, bucket,
562 (reason & REASON_MAIN_MASK), (reason & REASON_SUB_MASK));
563 if (DEBUG) {
564 Slog.d(TAG, "Moved " + packageName + " to bucket=" + bucket
565 + ", reason=0x0" + Integer.toHexString(reason));
566 }
567 }
568
Amith Yamasani17fffee2017-09-29 13:17:43 -0700569 @VisibleForTesting
570 File getUserFile(int userId) {
Amith Yamasania93542f2016-02-03 18:02:06 -0800571 return new File(new File(new File(mStorageDir, "users"),
572 Integer.toString(userId)), APP_IDLE_FILENAME);
573 }
574
Michael Wachenschwanzd1d8aa62019-02-28 16:38:37 -0800575 /**
576 * Check if App Idle File exists on disk
577 * @param userId
578 * @return true if file exists
579 */
580 public boolean userFileExists(int userId) {
581 return getUserFile(userId).exists();
582 }
583
Amith Yamasani17fffee2017-09-29 13:17:43 -0700584 private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) {
Amith Yamasania93542f2016-02-03 18:02:06 -0800585 FileInputStream fis = null;
586 try {
587 AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
588 fis = appIdleFile.openRead();
589 XmlPullParser parser = Xml.newPullParser();
590 parser.setInput(fis, StandardCharsets.UTF_8.name());
591
592 int type;
593 while ((type = parser.next()) != XmlPullParser.START_TAG
594 && type != XmlPullParser.END_DOCUMENT) {
595 // Skip
596 }
597
598 if (type != XmlPullParser.START_TAG) {
599 Slog.e(TAG, "Unable to read app idle file for user " + userId);
600 return;
601 }
602 if (!parser.getName().equals(TAG_PACKAGES)) {
603 return;
604 }
605 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
606 if (type == XmlPullParser.START_TAG) {
607 final String name = parser.getName();
608 if (name.equals(TAG_PACKAGE)) {
609 final String packageName = parser.getAttributeValue(null, ATTR_NAME);
Amith Yamasani17fffee2017-09-29 13:17:43 -0700610 AppUsageHistory appUsageHistory = new AppUsageHistory();
611 appUsageHistory.lastUsedElapsedTime =
Amith Yamasania93542f2016-02-03 18:02:06 -0800612 Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800613 appUsageHistory.lastUsedByUserElapsedTime = getLongValue(parser,
614 ATTR_LAST_USED_BY_USER_ELAPSED,
615 appUsageHistory.lastUsedElapsedTime);
Amith Yamasani17fffee2017-09-29 13:17:43 -0700616 appUsageHistory.lastUsedScreenTime =
Amith Yamasania93542f2016-02-03 18:02:06 -0800617 Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800618 appUsageHistory.lastPredictedTime = getLongValue(parser,
619 ATTR_LAST_PREDICTED_TIME, 0L);
Amith Yamasani17fffee2017-09-29 13:17:43 -0700620 String currentBucketString = parser.getAttributeValue(null,
621 ATTR_CURRENT_BUCKET);
622 appUsageHistory.currentBucket = currentBucketString == null
Amith Yamasani172612c2017-12-15 10:51:53 -0800623 ? STANDBY_BUCKET_ACTIVE
Amith Yamasani17fffee2017-09-29 13:17:43 -0700624 : Integer.parseInt(currentBucketString);
Amith Yamasani119be9a2018-02-18 22:23:00 -0800625 String bucketingReason =
Amith Yamasani17fffee2017-09-29 13:17:43 -0700626 parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800627 appUsageHistory.lastJobRunTime = getLongValue(parser,
628 ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE);
Amith Yamasanibbbad9c2018-02-10 16:46:38 -0800629 appUsageHistory.bucketActiveTimeoutTime = getLongValue(parser,
630 ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L);
631 appUsageHistory.bucketWorkingSetTimeoutTime = getLongValue(parser,
632 ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L);
Amith Yamasani119be9a2018-02-18 22:23:00 -0800633 appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT;
634 if (bucketingReason != null) {
635 try {
636 appUsageHistory.bucketingReason =
637 Integer.parseInt(bucketingReason, 16);
638 } catch (NumberFormatException nfe) {
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800639 Slog.wtf(TAG, "Unable to read bucketing reason", nfe);
640 }
641 }
642 appUsageHistory.lastRestrictAttemptElapsedTime =
643 getLongValue(parser, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, 0);
644 String lastRestrictReason = parser.getAttributeValue(
645 null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON);
646 if (lastRestrictReason != null) {
647 try {
648 appUsageHistory.lastRestrictReason =
649 Integer.parseInt(lastRestrictReason, 16);
650 } catch (NumberFormatException nfe) {
651 Slog.wtf(TAG, "Unable to read last restrict reason", nfe);
Amith Yamasani119be9a2018-02-18 22:23:00 -0800652 }
Amith Yamasani17fffee2017-09-29 13:17:43 -0700653 }
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800654 appUsageHistory.lastInformedBucket = -1;
Amith Yamasani17fffee2017-09-29 13:17:43 -0700655 userHistory.put(packageName, appUsageHistory);
Amith Yamasania93542f2016-02-03 18:02:06 -0800656 }
657 }
658 }
659 } catch (IOException | XmlPullParserException e) {
Michael Wachenschwanz16b2f2b2019-10-09 15:32:55 -0700660 Slog.e(TAG, "Unable to read app idle file for user " + userId, e);
Amith Yamasania93542f2016-02-03 18:02:06 -0800661 } finally {
662 IoUtils.closeQuietly(fis);
663 }
664 }
665
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800666 private long getLongValue(XmlPullParser parser, String attrName, long defValue) {
667 String value = parser.getAttributeValue(null, attrName);
668 if (value == null) return defValue;
669 return Long.parseLong(value);
670 }
671
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800672 public void writeAppIdleTimes(int userId) {
Amith Yamasania93542f2016-02-03 18:02:06 -0800673 FileOutputStream fos = null;
674 AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
675 try {
676 fos = appIdleFile.startWrite();
677 final BufferedOutputStream bos = new BufferedOutputStream(fos);
678
679 FastXmlSerializer xml = new FastXmlSerializer();
680 xml.setOutput(bos, StandardCharsets.UTF_8.name());
681 xml.startDocument(null, true);
682 xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
683
684 xml.startTag(null, TAG_PACKAGES);
685
Amith Yamasani17fffee2017-09-29 13:17:43 -0700686 ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId);
Amith Yamasania93542f2016-02-03 18:02:06 -0800687 final int N = userHistory.size();
688 for (int i = 0; i < N; i++) {
689 String packageName = userHistory.keyAt(i);
Michael Wachenschwanz16b2f2b2019-10-09 15:32:55 -0700690 // Skip any unexpected null package names
691 if (packageName == null) {
692 Slog.w(TAG, "Skipping App Idle write for unexpected null package");
693 continue;
694 }
Amith Yamasani17fffee2017-09-29 13:17:43 -0700695 AppUsageHistory history = userHistory.valueAt(i);
Amith Yamasania93542f2016-02-03 18:02:06 -0800696 xml.startTag(null, TAG_PACKAGE);
697 xml.attribute(null, ATTR_NAME, packageName);
698 xml.attribute(null, ATTR_ELAPSED_IDLE,
699 Long.toString(history.lastUsedElapsedTime));
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800700 xml.attribute(null, ATTR_LAST_USED_BY_USER_ELAPSED,
701 Long.toString(history.lastUsedByUserElapsedTime));
Amith Yamasania93542f2016-02-03 18:02:06 -0800702 xml.attribute(null, ATTR_SCREEN_IDLE,
703 Long.toString(history.lastUsedScreenTime));
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800704 xml.attribute(null, ATTR_LAST_PREDICTED_TIME,
705 Long.toString(history.lastPredictedTime));
Amith Yamasani17fffee2017-09-29 13:17:43 -0700706 xml.attribute(null, ATTR_CURRENT_BUCKET,
707 Integer.toString(history.currentBucket));
Amith Yamasani119be9a2018-02-18 22:23:00 -0800708 xml.attribute(null, ATTR_BUCKETING_REASON,
709 Integer.toHexString(history.bucketingReason));
Amith Yamasanibbbad9c2018-02-10 16:46:38 -0800710 if (history.bucketActiveTimeoutTime > 0) {
711 xml.attribute(null, ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, Long.toString(history
712 .bucketActiveTimeoutTime));
713 }
714 if (history.bucketWorkingSetTimeoutTime > 0) {
715 xml.attribute(null, ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, Long.toString(history
716 .bucketWorkingSetTimeoutTime));
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800717 }
718 if (history.lastJobRunTime != Long.MIN_VALUE) {
719 xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history
720 .lastJobRunTime));
721 }
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800722 if (history.lastRestrictAttemptElapsedTime > 0) {
723 xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED,
724 Long.toString(history.lastRestrictAttemptElapsedTime));
725 }
726 xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON,
727 Integer.toHexString(history.lastRestrictReason));
Amith Yamasania93542f2016-02-03 18:02:06 -0800728 xml.endTag(null, TAG_PACKAGE);
729 }
730
731 xml.endTag(null, TAG_PACKAGES);
732 xml.endDocument();
733 appIdleFile.finishWrite(fos);
734 } catch (Exception e) {
735 appIdleFile.failWrite(fos);
Michael Wachenschwanz16b2f2b2019-10-09 15:32:55 -0700736 Slog.e(TAG, "Error writing app idle file for user " + userId, e);
Amith Yamasania93542f2016-02-03 18:02:06 -0800737 }
Amith Yamasani0a11e692015-05-08 16:36:21 -0700738 }
739
Sudheer Shankab497dd42020-02-16 22:23:24 -0800740 public void dump(IndentingPrintWriter idpw, int userId, List<String> pkgs) {
Amith Yamasani119be9a2018-02-18 22:23:00 -0800741 idpw.println("App Standby States:");
Amith Yamasania93542f2016-02-03 18:02:06 -0800742 idpw.increaseIndent();
Amith Yamasani17fffee2017-09-29 13:17:43 -0700743 ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
Amith Yamasania93542f2016-02-03 18:02:06 -0800744 final long elapsedRealtime = SystemClock.elapsedRealtime();
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800745 final long totalElapsedTime = getElapsedTime(elapsedRealtime);
746 final long screenOnTime = getScreenOnTime(elapsedRealtime);
Amith Yamasani0a11e692015-05-08 16:36:21 -0700747 if (userHistory == null) return;
748 final int P = userHistory.size();
749 for (int p = 0; p < P; p++) {
750 final String packageName = userHistory.keyAt(p);
Amith Yamasani17fffee2017-09-29 13:17:43 -0700751 final AppUsageHistory appUsageHistory = userHistory.valueAt(p);
Sudheer Shankab497dd42020-02-16 22:23:24 -0800752 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(packageName)) {
Dianne Hackbornc81983a2017-10-20 16:16:32 -0700753 continue;
754 }
Amith Yamasania93542f2016-02-03 18:02:06 -0800755 idpw.print("package=" + packageName);
Amith Yamasani119be9a2018-02-18 22:23:00 -0800756 idpw.print(" u=" + userId);
757 idpw.print(" bucket=" + appUsageHistory.currentBucket
758 + " reason="
759 + UsageStatsManager.reasonToString(appUsageHistory.bucketingReason));
760 idpw.print(" used=");
Amith Yamasani17fffee2017-09-29 13:17:43 -0700761 TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw);
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800762 idpw.print(" usedByUser=");
763 TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedByUserElapsedTime,
764 idpw);
Amith Yamasani119be9a2018-02-18 22:23:00 -0800765 idpw.print(" usedScr=");
Amith Yamasani17fffee2017-09-29 13:17:43 -0700766 TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
Amith Yamasani119be9a2018-02-18 22:23:00 -0800767 idpw.print(" lastPred=");
Amith Yamasanibd7b3022017-12-06 17:40:25 -0800768 TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw);
Amith Yamasani119be9a2018-02-18 22:23:00 -0800769 idpw.print(" activeLeft=");
770 TimeUtils.formatDuration(appUsageHistory.bucketActiveTimeoutTime - totalElapsedTime,
Amith Yamasanibbbad9c2018-02-10 16:46:38 -0800771 idpw);
Amith Yamasani119be9a2018-02-18 22:23:00 -0800772 idpw.print(" wsLeft=");
773 TimeUtils.formatDuration(appUsageHistory.bucketWorkingSetTimeoutTime - totalElapsedTime,
Amith Yamasanibbbad9c2018-02-10 16:46:38 -0800774 idpw);
Amith Yamasani119be9a2018-02-18 22:23:00 -0800775 idpw.print(" lastJob=");
Amith Yamasani53f06ea2018-01-05 17:53:46 -0800776 TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
Kweku Adamsc6a9b342020-01-08 18:37:26 -0800777 if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) {
778 idpw.print(" lastRestrictAttempt=");
779 TimeUtils.formatDuration(
780 totalElapsedTime - appUsageHistory.lastRestrictAttemptElapsedTime, idpw);
781 idpw.print(" lastRestrictReason="
782 + UsageStatsManager.reasonToString(appUsageHistory.lastRestrictReason));
783 }
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800784 idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
Amith Yamasania93542f2016-02-03 18:02:06 -0800785 idpw.println();
786 }
787 idpw.println();
788 idpw.print("totalElapsedTime=");
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800789 TimeUtils.formatDuration(getElapsedTime(elapsedRealtime), idpw);
Amith Yamasania93542f2016-02-03 18:02:06 -0800790 idpw.println();
791 idpw.print("totalScreenOnTime=");
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800792 TimeUtils.formatDuration(getScreenOnTime(elapsedRealtime), idpw);
Amith Yamasania93542f2016-02-03 18:02:06 -0800793 idpw.println();
794 idpw.decreaseIndent();
795 }
Amith Yamasania93542f2016-02-03 18:02:06 -0800796}