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