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 android.os.Environment; 20 import android.os.SystemClock; 21 import android.util.ArrayMap; 22 import android.util.AtomicFile; 23 import android.util.Slog; 24 import android.util.SparseArray; 25 import android.util.TimeUtils; 26 import android.util.Xml; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.util.FastXmlSerializer; 30 import com.android.internal.util.IndentingPrintWriter; 31 32 import libcore.io.IoUtils; 33 34 import org.xmlpull.v1.XmlPullParser; 35 import org.xmlpull.v1.XmlPullParserException; 36 37 import java.io.BufferedOutputStream; 38 import java.io.BufferedReader; 39 import java.io.File; 40 import java.io.FileInputStream; 41 import java.io.FileOutputStream; 42 import java.io.FileReader; 43 import java.io.IOException; 44 import java.nio.charset.StandardCharsets; 45 46 /** 47 * Keeps track of recent active state changes in apps. 48 * Access should be guarded by a lock by the caller. 49 */ 50 public class AppIdleHistory { 51 52 private static final String TAG = "AppIdleHistory"; 53 54 // History for all users and all packages 55 private SparseArray<ArrayMap<String,PackageHistory>> mIdleHistory = new SparseArray<>(); 56 private long mLastPeriod = 0; 57 private static final long ONE_MINUTE = 60 * 1000; 58 private static final int HISTORY_SIZE = 100; 59 private static final int FLAG_LAST_STATE = 2; 60 private static final int FLAG_PARTIAL_ACTIVE = 1; 61 private static final long PERIOD_DURATION = UsageStatsService.COMPRESS_TIME ? ONE_MINUTE 62 : 60 * ONE_MINUTE; 63 64 @VisibleForTesting 65 static final String APP_IDLE_FILENAME = "app_idle_stats.xml"; 66 private static final String TAG_PACKAGES = "packages"; 67 private static final String TAG_PACKAGE = "package"; 68 private static final String ATTR_NAME = "name"; 69 // Screen on timebase time when app was last used 70 private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; 71 // Elapsed timebase time when app was last used 72 private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime"; 73 74 // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) 75 private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration 76 private long mElapsedDuration; // Total device on duration since device was "born" 77 78 // screen on time = mScreenOnDuration + (timeNow - mScreenOnSnapshot) 79 private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration 80 private long mScreenOnDuration; // Total screen on duration since device was "born" 81 82 private long mElapsedTimeThreshold; 83 private long mScreenOnTimeThreshold; 84 private final File mStorageDir; 85 86 private boolean mScreenOn; 87 88 private static class PackageHistory { 89 final byte[] recent = new byte[HISTORY_SIZE]; 90 long lastUsedElapsedTime; 91 long lastUsedScreenTime; 92 } 93 AppIdleHistory(long elapsedRealtime)94 AppIdleHistory(long elapsedRealtime) { 95 this(Environment.getDataSystemDirectory(), elapsedRealtime); 96 } 97 98 @VisibleForTesting AppIdleHistory(File storageDir, long elapsedRealtime)99 AppIdleHistory(File storageDir, long elapsedRealtime) { 100 mElapsedSnapshot = elapsedRealtime; 101 mScreenOnSnapshot = elapsedRealtime; 102 mStorageDir = storageDir; 103 readScreenOnTime(); 104 } 105 setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold)106 public void setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold) { 107 mElapsedTimeThreshold = elapsedTimeThreshold; 108 mScreenOnTimeThreshold = screenOnTimeThreshold; 109 } 110 updateDisplay(boolean screenOn, long elapsedRealtime)111 public void updateDisplay(boolean screenOn, long elapsedRealtime) { 112 if (screenOn == mScreenOn) return; 113 114 mScreenOn = screenOn; 115 if (mScreenOn) { 116 mScreenOnSnapshot = elapsedRealtime; 117 } else { 118 mScreenOnDuration += elapsedRealtime - mScreenOnSnapshot; 119 mElapsedDuration += elapsedRealtime - mElapsedSnapshot; 120 mElapsedSnapshot = elapsedRealtime; 121 } 122 } 123 getScreenOnTime(long elapsedRealtime)124 public long getScreenOnTime(long elapsedRealtime) { 125 long screenOnTime = mScreenOnDuration; 126 if (mScreenOn) { 127 screenOnTime += elapsedRealtime - mScreenOnSnapshot; 128 } 129 return screenOnTime; 130 } 131 132 @VisibleForTesting getScreenOnTimeFile()133 File getScreenOnTimeFile() { 134 return new File(mStorageDir, "screen_on_time"); 135 } 136 readScreenOnTime()137 private void readScreenOnTime() { 138 File screenOnTimeFile = getScreenOnTimeFile(); 139 if (screenOnTimeFile.exists()) { 140 try { 141 BufferedReader reader = new BufferedReader(new FileReader(screenOnTimeFile)); 142 mScreenOnDuration = Long.parseLong(reader.readLine()); 143 mElapsedDuration = Long.parseLong(reader.readLine()); 144 reader.close(); 145 } catch (IOException | NumberFormatException e) { 146 } 147 } else { 148 writeScreenOnTime(); 149 } 150 } 151 writeScreenOnTime()152 private void writeScreenOnTime() { 153 AtomicFile screenOnTimeFile = new AtomicFile(getScreenOnTimeFile()); 154 FileOutputStream fos = null; 155 try { 156 fos = screenOnTimeFile.startWrite(); 157 fos.write((Long.toString(mScreenOnDuration) + "\n" 158 + Long.toString(mElapsedDuration) + "\n").getBytes()); 159 screenOnTimeFile.finishWrite(fos); 160 } catch (IOException ioe) { 161 screenOnTimeFile.failWrite(fos); 162 } 163 } 164 165 /** 166 * To be called periodically to keep track of elapsed time when app idle times are written 167 */ writeAppIdleDurations()168 public void writeAppIdleDurations() { 169 final long elapsedRealtime = SystemClock.elapsedRealtime(); 170 // Only bump up and snapshot the elapsed time. Don't change screen on duration. 171 mElapsedDuration += elapsedRealtime - mElapsedSnapshot; 172 mElapsedSnapshot = elapsedRealtime; 173 writeScreenOnTime(); 174 } 175 reportUsage(String packageName, int userId, long elapsedRealtime)176 public void reportUsage(String packageName, int userId, long elapsedRealtime) { 177 ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); 178 PackageHistory packageHistory = getPackageHistory(userHistory, packageName, 179 elapsedRealtime); 180 181 shiftHistoryToNow(userHistory, elapsedRealtime); 182 183 packageHistory.lastUsedElapsedTime = mElapsedDuration 184 + (elapsedRealtime - mElapsedSnapshot); 185 packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); 186 packageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE; 187 } 188 setIdle(String packageName, int userId, long elapsedRealtime)189 public void setIdle(String packageName, int userId, long elapsedRealtime) { 190 ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); 191 PackageHistory packageHistory = getPackageHistory(userHistory, packageName, 192 elapsedRealtime); 193 194 shiftHistoryToNow(userHistory, elapsedRealtime); 195 196 packageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE; 197 } 198 shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory, long elapsedRealtime)199 private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory, 200 long elapsedRealtime) { 201 long thisPeriod = elapsedRealtime / PERIOD_DURATION; 202 // Has the period switched over? Slide all users' package histories 203 if (mLastPeriod != 0 && mLastPeriod < thisPeriod 204 && (thisPeriod - mLastPeriod) < HISTORY_SIZE - 1) { 205 int diff = (int) (thisPeriod - mLastPeriod); 206 final int NUSERS = mIdleHistory.size(); 207 for (int u = 0; u < NUSERS; u++) { 208 userHistory = mIdleHistory.valueAt(u); 209 for (PackageHistory idleState : userHistory.values()) { 210 // Shift left 211 System.arraycopy(idleState.recent, diff, idleState.recent, 0, 212 HISTORY_SIZE - diff); 213 // Replicate last state across the diff 214 for (int i = 0; i < diff; i++) { 215 idleState.recent[HISTORY_SIZE - i - 1] = 216 (byte) (idleState.recent[HISTORY_SIZE - diff - 1] & FLAG_LAST_STATE); 217 } 218 } 219 } 220 } 221 mLastPeriod = thisPeriod; 222 } 223 getUserHistory(int userId)224 private ArrayMap<String, PackageHistory> getUserHistory(int userId) { 225 ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); 226 if (userHistory == null) { 227 userHistory = new ArrayMap<>(); 228 mIdleHistory.put(userId, userHistory); 229 readAppIdleTimes(userId, userHistory); 230 } 231 return userHistory; 232 } 233 getPackageHistory(ArrayMap<String, PackageHistory> userHistory, String packageName, long elapsedRealtime)234 private PackageHistory getPackageHistory(ArrayMap<String, PackageHistory> userHistory, 235 String packageName, long elapsedRealtime) { 236 PackageHistory packageHistory = userHistory.get(packageName); 237 if (packageHistory == null) { 238 packageHistory = new PackageHistory(); 239 packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime); 240 packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); 241 userHistory.put(packageName, packageHistory); 242 } 243 return packageHistory; 244 } 245 onUserRemoved(int userId)246 public void onUserRemoved(int userId) { 247 mIdleHistory.remove(userId); 248 } 249 isIdle(String packageName, int userId, long elapsedRealtime)250 public boolean isIdle(String packageName, int userId, long elapsedRealtime) { 251 ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); 252 PackageHistory packageHistory = 253 getPackageHistory(userHistory, packageName, elapsedRealtime); 254 if (packageHistory == null) { 255 return false; // Default to not idle 256 } else { 257 return hasPassedThresholds(packageHistory, elapsedRealtime); 258 } 259 } 260 getElapsedTime(long elapsedRealtime)261 private long getElapsedTime(long elapsedRealtime) { 262 return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration); 263 } 264 setIdle(String packageName, int userId, boolean idle, long elapsedRealtime)265 public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) { 266 ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); 267 PackageHistory packageHistory = getPackageHistory(userHistory, packageName, 268 elapsedRealtime); 269 packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime) 270 - mElapsedTimeThreshold; 271 packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime) 272 - (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */; 273 } 274 clearUsage(String packageName, int userId)275 public void clearUsage(String packageName, int userId) { 276 ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId); 277 userHistory.remove(packageName); 278 } 279 hasPassedThresholds(PackageHistory packageHistory, long elapsedRealtime)280 private boolean hasPassedThresholds(PackageHistory packageHistory, long elapsedRealtime) { 281 return (packageHistory.lastUsedScreenTime 282 <= getScreenOnTime(elapsedRealtime) - mScreenOnTimeThreshold) 283 && (packageHistory.lastUsedElapsedTime 284 <= getElapsedTime(elapsedRealtime) - mElapsedTimeThreshold); 285 } 286 getUserFile(int userId)287 private File getUserFile(int userId) { 288 return new File(new File(new File(mStorageDir, "users"), 289 Integer.toString(userId)), APP_IDLE_FILENAME); 290 } 291 readAppIdleTimes(int userId, ArrayMap<String, PackageHistory> userHistory)292 private void readAppIdleTimes(int userId, ArrayMap<String, PackageHistory> userHistory) { 293 FileInputStream fis = null; 294 try { 295 AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); 296 fis = appIdleFile.openRead(); 297 XmlPullParser parser = Xml.newPullParser(); 298 parser.setInput(fis, StandardCharsets.UTF_8.name()); 299 300 int type; 301 while ((type = parser.next()) != XmlPullParser.START_TAG 302 && type != XmlPullParser.END_DOCUMENT) { 303 // Skip 304 } 305 306 if (type != XmlPullParser.START_TAG) { 307 Slog.e(TAG, "Unable to read app idle file for user " + userId); 308 return; 309 } 310 if (!parser.getName().equals(TAG_PACKAGES)) { 311 return; 312 } 313 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 314 if (type == XmlPullParser.START_TAG) { 315 final String name = parser.getName(); 316 if (name.equals(TAG_PACKAGE)) { 317 final String packageName = parser.getAttributeValue(null, ATTR_NAME); 318 PackageHistory packageHistory = new PackageHistory(); 319 packageHistory.lastUsedElapsedTime = 320 Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE)); 321 packageHistory.lastUsedScreenTime = 322 Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE)); 323 userHistory.put(packageName, packageHistory); 324 } 325 } 326 } 327 } catch (IOException | XmlPullParserException e) { 328 Slog.e(TAG, "Unable to read app idle file for user " + userId); 329 } finally { 330 IoUtils.closeQuietly(fis); 331 } 332 } 333 writeAppIdleTimes(int userId)334 public void writeAppIdleTimes(int userId) { 335 FileOutputStream fos = null; 336 AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); 337 try { 338 fos = appIdleFile.startWrite(); 339 final BufferedOutputStream bos = new BufferedOutputStream(fos); 340 341 FastXmlSerializer xml = new FastXmlSerializer(); 342 xml.setOutput(bos, StandardCharsets.UTF_8.name()); 343 xml.startDocument(null, true); 344 xml.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 345 346 xml.startTag(null, TAG_PACKAGES); 347 348 ArrayMap<String,PackageHistory> userHistory = getUserHistory(userId); 349 final int N = userHistory.size(); 350 for (int i = 0; i < N; i++) { 351 String packageName = userHistory.keyAt(i); 352 PackageHistory history = userHistory.valueAt(i); 353 xml.startTag(null, TAG_PACKAGE); 354 xml.attribute(null, ATTR_NAME, packageName); 355 xml.attribute(null, ATTR_ELAPSED_IDLE, 356 Long.toString(history.lastUsedElapsedTime)); 357 xml.attribute(null, ATTR_SCREEN_IDLE, 358 Long.toString(history.lastUsedScreenTime)); 359 xml.endTag(null, TAG_PACKAGE); 360 } 361 362 xml.endTag(null, TAG_PACKAGES); 363 xml.endDocument(); 364 appIdleFile.finishWrite(fos); 365 } catch (Exception e) { 366 appIdleFile.failWrite(fos); 367 Slog.e(TAG, "Error writing app idle file for user " + userId); 368 } 369 } 370 dump(IndentingPrintWriter idpw, int userId)371 public void dump(IndentingPrintWriter idpw, int userId) { 372 idpw.println("Package idle stats:"); 373 idpw.increaseIndent(); 374 ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); 375 final long elapsedRealtime = SystemClock.elapsedRealtime(); 376 final long totalElapsedTime = getElapsedTime(elapsedRealtime); 377 final long screenOnTime = getScreenOnTime(elapsedRealtime); 378 if (userHistory == null) return; 379 final int P = userHistory.size(); 380 for (int p = 0; p < P; p++) { 381 final String packageName = userHistory.keyAt(p); 382 final PackageHistory packageHistory = userHistory.valueAt(p); 383 idpw.print("package=" + packageName); 384 idpw.print(" lastUsedElapsed="); 385 TimeUtils.formatDuration(totalElapsedTime - packageHistory.lastUsedElapsedTime, idpw); 386 idpw.print(" lastUsedScreenOn="); 387 TimeUtils.formatDuration(screenOnTime - packageHistory.lastUsedScreenTime, idpw); 388 idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); 389 idpw.println(); 390 } 391 idpw.println(); 392 idpw.print("totalElapsedTime="); 393 TimeUtils.formatDuration(getElapsedTime(elapsedRealtime), idpw); 394 idpw.println(); 395 idpw.print("totalScreenOnTime="); 396 TimeUtils.formatDuration(getScreenOnTime(elapsedRealtime), idpw); 397 idpw.println(); 398 idpw.decreaseIndent(); 399 } 400 dumpHistory(IndentingPrintWriter idpw, int userId)401 public void dumpHistory(IndentingPrintWriter idpw, int userId) { 402 ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId); 403 final long elapsedRealtime = SystemClock.elapsedRealtime(); 404 if (userHistory == null) return; 405 final int P = userHistory.size(); 406 for (int p = 0; p < P; p++) { 407 final String packageName = userHistory.keyAt(p); 408 final byte[] history = userHistory.valueAt(p).recent; 409 for (int i = 0; i < HISTORY_SIZE; i++) { 410 idpw.print(history[i] == 0 ? '.' : 'A'); 411 } 412 idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); 413 idpw.print(" " + packageName); 414 idpw.println(); 415 } 416 } 417 } 418