• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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