Home | History | Annotate | Download | only in am
      1 /*
      2  * Copyright (C) 2006-2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.am;
     18 
     19 import android.app.AppGlobals;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.pm.IPackageManager;
     23 import android.content.pm.PackageInfo;
     24 import android.content.pm.PackageManager;
     25 import android.os.Binder;
     26 import android.os.IBinder;
     27 import android.os.FileUtils;
     28 import android.os.Parcel;
     29 import android.os.Process;
     30 import android.os.RemoteException;
     31 import android.os.ServiceManager;
     32 import android.os.SystemClock;
     33 import android.util.ArrayMap;
     34 import android.util.AtomicFile;
     35 import android.util.Slog;
     36 import android.util.Xml;
     37 
     38 import com.android.internal.app.IUsageStats;
     39 import com.android.internal.content.PackageMonitor;
     40 import com.android.internal.os.PkgUsageStats;
     41 import com.android.internal.util.FastXmlSerializer;
     42 
     43 import org.xmlpull.v1.XmlPullParser;
     44 import org.xmlpull.v1.XmlPullParserException;
     45 import org.xmlpull.v1.XmlSerializer;
     46 
     47 import java.io.File;
     48 import java.io.FileDescriptor;
     49 import java.io.FileInputStream;
     50 import java.io.FileNotFoundException;
     51 import java.io.FileOutputStream;
     52 import java.io.IOException;
     53 import java.io.PrintWriter;
     54 import java.util.ArrayList;
     55 import java.util.Calendar;
     56 import java.util.Collections;
     57 import java.util.HashMap;
     58 import java.util.HashSet;
     59 import java.util.List;
     60 import java.util.Map;
     61 import java.util.Set;
     62 import java.util.TimeZone;
     63 import java.util.concurrent.atomic.AtomicBoolean;
     64 import java.util.concurrent.atomic.AtomicInteger;
     65 import java.util.concurrent.atomic.AtomicLong;
     66 
     67 /**
     68  * This service collects the statistics associated with usage
     69  * of various components, like when a particular package is launched or
     70  * paused and aggregates events like number of time a component is launched
     71  * total duration of a component launch.
     72  */
     73 public final class UsageStatsService extends IUsageStats.Stub {
     74     public static final String SERVICE_NAME = "usagestats";
     75     private static final boolean localLOGV = false;
     76     private static final boolean REPORT_UNEXPECTED = false;
     77     private static final String TAG = "UsageStats";
     78 
     79     // Current on-disk Parcel version
     80     private static final int VERSION = 1008;
     81 
     82     private static final int CHECKIN_VERSION = 4;
     83 
     84     private static final String FILE_PREFIX = "usage-";
     85 
     86     private static final String FILE_HISTORY = FILE_PREFIX + "history.xml";
     87 
     88     private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms
     89 
     90     private static final int MAX_NUM_FILES = 5;
     91 
     92     private static final int NUM_LAUNCH_TIME_BINS = 10;
     93     private static final int[] LAUNCH_TIME_BINS = {
     94         250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000
     95     };
     96 
     97     static IUsageStats sService;
     98     private Context mContext;
     99     // structure used to maintain statistics since the last checkin.
    100     final private ArrayMap<String, PkgUsageStatsExtended> mStats;
    101 
    102     // Maintains the last time any component was resumed, for all time.
    103     final private ArrayMap<String, ArrayMap<String, Long>> mLastResumeTimes;
    104 
    105     // To remove last-resume time stats when a pacakge is removed.
    106     private PackageMonitor mPackageMonitor;
    107 
    108     // Lock to update package stats. Methods suffixed by SLOCK should invoked with
    109     // this lock held
    110     final Object mStatsLock;
    111     // Lock to write to file. Methods suffixed by FLOCK should invoked with
    112     // this lock held.
    113     final Object mFileLock;
    114     // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks
    115     private String mLastResumedPkg;
    116     private String mLastResumedComp;
    117     private boolean mIsResumed;
    118     private File mFile;
    119     private AtomicFile mHistoryFile;
    120     private String mFileLeaf;
    121     private File mDir;
    122 
    123     private Calendar mCal; // guarded by itself
    124 
    125     private final AtomicInteger mLastWriteDay = new AtomicInteger(-1);
    126     private final AtomicLong mLastWriteElapsedTime = new AtomicLong(0);
    127     private final AtomicBoolean mUnforcedDiskWriteRunning = new AtomicBoolean(false);
    128 
    129     static class TimeStats {
    130         int count;
    131         int[] times = new int[NUM_LAUNCH_TIME_BINS];
    132 
    133         TimeStats() {
    134         }
    135 
    136         void incCount() {
    137             count++;
    138         }
    139 
    140         void add(int val) {
    141             final int[] bins = LAUNCH_TIME_BINS;
    142             for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
    143                 if (val < bins[i]) {
    144                     times[i]++;
    145                     return;
    146                 }
    147             }
    148             times[NUM_LAUNCH_TIME_BINS-1]++;
    149         }
    150 
    151         TimeStats(Parcel in) {
    152             count = in.readInt();
    153             final int[] localTimes = times;
    154             for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
    155                 localTimes[i] = in.readInt();
    156             }
    157         }
    158 
    159         void writeToParcel(Parcel out) {
    160             out.writeInt(count);
    161             final int[] localTimes = times;
    162             for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
    163                 out.writeInt(localTimes[i]);
    164             }
    165         }
    166     }
    167 
    168     private class PkgUsageStatsExtended {
    169         final ArrayMap<String, TimeStats> mLaunchTimes
    170                 = new ArrayMap<String, TimeStats>();
    171         final ArrayMap<String, TimeStats> mFullyDrawnTimes
    172                 = new ArrayMap<String, TimeStats>();
    173         int mLaunchCount;
    174         long mUsageTime;
    175         long mPausedTime;
    176         long mResumedTime;
    177 
    178         PkgUsageStatsExtended() {
    179             mLaunchCount = 0;
    180             mUsageTime = 0;
    181         }
    182 
    183         PkgUsageStatsExtended(Parcel in) {
    184             mLaunchCount = in.readInt();
    185             mUsageTime = in.readLong();
    186             if (localLOGV) Slog.v(TAG, "Launch count: " + mLaunchCount
    187                     + ", Usage time:" + mUsageTime);
    188 
    189             final int numLaunchTimeStats = in.readInt();
    190             if (localLOGV) Slog.v(TAG, "Reading launch times: " + numLaunchTimeStats);
    191             mLaunchTimes.ensureCapacity(numLaunchTimeStats);
    192             for (int i=0; i<numLaunchTimeStats; i++) {
    193                 String comp = in.readString();
    194                 if (localLOGV) Slog.v(TAG, "Component: " + comp);
    195                 TimeStats times = new TimeStats(in);
    196                 mLaunchTimes.put(comp, times);
    197             }
    198 
    199             final int numFullyDrawnTimeStats = in.readInt();
    200             if (localLOGV) Slog.v(TAG, "Reading fully drawn times: " + numFullyDrawnTimeStats);
    201             mFullyDrawnTimes.ensureCapacity(numFullyDrawnTimeStats);
    202             for (int i=0; i<numFullyDrawnTimeStats; i++) {
    203                 String comp = in.readString();
    204                 if (localLOGV) Slog.v(TAG, "Component: " + comp);
    205                 TimeStats times = new TimeStats(in);
    206                 mFullyDrawnTimes.put(comp, times);
    207             }
    208         }
    209 
    210         void updateResume(String comp, boolean launched) {
    211             if (launched) {
    212                 mLaunchCount ++;
    213             }
    214             mResumedTime = SystemClock.elapsedRealtime();
    215         }
    216 
    217         void updatePause() {
    218             mPausedTime =  SystemClock.elapsedRealtime();
    219             mUsageTime += (mPausedTime - mResumedTime);
    220         }
    221 
    222         void addLaunchCount(String comp) {
    223             TimeStats times = mLaunchTimes.get(comp);
    224             if (times == null) {
    225                 times = new TimeStats();
    226                 mLaunchTimes.put(comp, times);
    227             }
    228             times.incCount();
    229         }
    230 
    231         void addLaunchTime(String comp, int millis) {
    232             TimeStats times = mLaunchTimes.get(comp);
    233             if (times == null) {
    234                 times = new TimeStats();
    235                 mLaunchTimes.put(comp, times);
    236             }
    237             times.add(millis);
    238         }
    239 
    240         void addFullyDrawnTime(String comp, int millis) {
    241             TimeStats times = mFullyDrawnTimes.get(comp);
    242             if (times == null) {
    243                 times = new TimeStats();
    244                 mFullyDrawnTimes.put(comp, times);
    245             }
    246             times.add(millis);
    247         }
    248 
    249         void writeToParcel(Parcel out) {
    250             out.writeInt(mLaunchCount);
    251             out.writeLong(mUsageTime);
    252             final int numLaunchTimeStats = mLaunchTimes.size();
    253             out.writeInt(numLaunchTimeStats);
    254             for (int i=0; i<numLaunchTimeStats; i++) {
    255                 out.writeString(mLaunchTimes.keyAt(i));
    256                 mLaunchTimes.valueAt(i).writeToParcel(out);
    257             }
    258             final int numFullyDrawnTimeStats = mFullyDrawnTimes.size();
    259             out.writeInt(numFullyDrawnTimeStats);
    260             for (int i=0; i<numFullyDrawnTimeStats; i++) {
    261                 out.writeString(mFullyDrawnTimes.keyAt(i));
    262                 mFullyDrawnTimes.valueAt(i).writeToParcel(out);
    263             }
    264         }
    265 
    266         void clear() {
    267             mLaunchTimes.clear();
    268             mFullyDrawnTimes.clear();
    269             mLaunchCount = 0;
    270             mUsageTime = 0;
    271         }
    272     }
    273 
    274     UsageStatsService(String dir) {
    275         mStats = new ArrayMap<String, PkgUsageStatsExtended>();
    276         mLastResumeTimes = new ArrayMap<String, ArrayMap<String, Long>>();
    277         mStatsLock = new Object();
    278         mFileLock = new Object();
    279         mDir = new File(dir);
    280         mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
    281 
    282         mDir.mkdir();
    283 
    284         // Remove any old usage files from previous versions.
    285         File parentDir = mDir.getParentFile();
    286         String fList[] = parentDir.list();
    287         if (fList != null) {
    288             String prefix = mDir.getName() + ".";
    289             int i = fList.length;
    290             while (i > 0) {
    291                 i--;
    292                 if (fList[i].startsWith(prefix)) {
    293                     Slog.i(TAG, "Deleting old usage file: " + fList[i]);
    294                     (new File(parentDir, fList[i])).delete();
    295                 }
    296             }
    297         }
    298 
    299         // Update current stats which are binned by date
    300         mFileLeaf = getCurrentDateStr(FILE_PREFIX);
    301         mFile = new File(mDir, mFileLeaf);
    302         mHistoryFile = new AtomicFile(new File(mDir, FILE_HISTORY));
    303         readStatsFromFile();
    304         readHistoryStatsFromFile();
    305         mLastWriteElapsedTime.set(SystemClock.elapsedRealtime());
    306         // mCal was set by getCurrentDateStr(), want to use that same time.
    307         mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR));
    308     }
    309 
    310     /*
    311      * Utility method to convert date into string.
    312      */
    313     private String getCurrentDateStr(String prefix) {
    314         StringBuilder sb = new StringBuilder();
    315         synchronized (mCal) {
    316             mCal.setTimeInMillis(System.currentTimeMillis());
    317             if (prefix != null) {
    318                 sb.append(prefix);
    319             }
    320             sb.append(mCal.get(Calendar.YEAR));
    321             int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1;
    322             if (mm < 10) {
    323                 sb.append("0");
    324             }
    325             sb.append(mm);
    326             int dd = mCal.get(Calendar.DAY_OF_MONTH);
    327             if (dd < 10) {
    328                 sb.append("0");
    329             }
    330             sb.append(dd);
    331         }
    332         return sb.toString();
    333     }
    334 
    335     private Parcel getParcelForFile(File file) throws IOException {
    336         FileInputStream stream = new FileInputStream(file);
    337         byte[] raw = readFully(stream);
    338         Parcel in = Parcel.obtain();
    339         in.unmarshall(raw, 0, raw.length);
    340         in.setDataPosition(0);
    341         stream.close();
    342         return in;
    343     }
    344 
    345     private void readStatsFromFile() {
    346         File newFile = mFile;
    347         synchronized (mFileLock) {
    348             try {
    349                 if (newFile.exists()) {
    350                     readStatsFLOCK(newFile);
    351                 } else {
    352                     // Check for file limit before creating a new file
    353                     checkFileLimitFLOCK();
    354                     newFile.createNewFile();
    355                 }
    356             } catch (IOException e) {
    357                 Slog.w(TAG,"Error : " + e + " reading data from file:" + newFile);
    358             }
    359         }
    360     }
    361 
    362     private void readStatsFLOCK(File file) throws IOException {
    363         Parcel in = getParcelForFile(file);
    364         int vers = in.readInt();
    365         if (vers != VERSION) {
    366             Slog.w(TAG, "Usage stats version changed; dropping");
    367             return;
    368         }
    369         int N = in.readInt();
    370         while (N > 0) {
    371             N--;
    372             String pkgName = in.readString();
    373             if (pkgName == null) {
    374                 break;
    375             }
    376             if (localLOGV) Slog.v(TAG, "Reading package #" + N + ": " + pkgName);
    377             PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
    378             synchronized (mStatsLock) {
    379                 mStats.put(pkgName, pus);
    380             }
    381         }
    382     }
    383 
    384     private void readHistoryStatsFromFile() {
    385         synchronized (mFileLock) {
    386             if (mHistoryFile.getBaseFile().exists()) {
    387                 readHistoryStatsFLOCK(mHistoryFile);
    388             }
    389         }
    390     }
    391 
    392     private void readHistoryStatsFLOCK(AtomicFile file) {
    393         FileInputStream fis = null;
    394         try {
    395             fis = mHistoryFile.openRead();
    396             XmlPullParser parser = Xml.newPullParser();
    397             parser.setInput(fis, null);
    398             int eventType = parser.getEventType();
    399             while (eventType != XmlPullParser.START_TAG) {
    400                 eventType = parser.next();
    401             }
    402             String tagName = parser.getName();
    403             if ("usage-history".equals(tagName)) {
    404                 String pkg = null;
    405                 do {
    406                     eventType = parser.next();
    407                     if (eventType == XmlPullParser.START_TAG) {
    408                         tagName = parser.getName();
    409                         int depth = parser.getDepth();
    410                         if ("pkg".equals(tagName) && depth == 2) {
    411                             pkg = parser.getAttributeValue(null, "name");
    412                         } else if ("comp".equals(tagName) && depth == 3 && pkg != null) {
    413                             String comp = parser.getAttributeValue(null, "name");
    414                             String lastResumeTimeStr = parser.getAttributeValue(null, "lrt");
    415                             if (comp != null && lastResumeTimeStr != null) {
    416                                 try {
    417                                     long lastResumeTime = Long.parseLong(lastResumeTimeStr);
    418                                     synchronized (mStatsLock) {
    419                                         ArrayMap<String, Long> lrt = mLastResumeTimes.get(pkg);
    420                                         if (lrt == null) {
    421                                             lrt = new ArrayMap<String, Long>();
    422                                             mLastResumeTimes.put(pkg, lrt);
    423                                         }
    424                                         lrt.put(comp, lastResumeTime);
    425                                     }
    426                                 } catch (NumberFormatException e) {
    427                                 }
    428                             }
    429                         }
    430                     } else if (eventType == XmlPullParser.END_TAG) {
    431                         if ("pkg".equals(parser.getName())) {
    432                             pkg = null;
    433                         }
    434                     }
    435                 } while (eventType != XmlPullParser.END_DOCUMENT);
    436             }
    437         } catch (XmlPullParserException e) {
    438             Slog.w(TAG,"Error reading history stats: " + e);
    439         } catch (IOException e) {
    440             Slog.w(TAG,"Error reading history stats: " + e);
    441         } finally {
    442             if (fis != null) {
    443                 try {
    444                     fis.close();
    445                 } catch (IOException e) {
    446                 }
    447             }
    448         }
    449     }
    450 
    451     private ArrayList<String> getUsageStatsFileListFLOCK() {
    452         // Check if there are too many files in the system and delete older files
    453         String fList[] = mDir.list();
    454         if (fList == null) {
    455             return null;
    456         }
    457         ArrayList<String> fileList = new ArrayList<String>();
    458         for (String file : fList) {
    459             if (!file.startsWith(FILE_PREFIX)) {
    460                 continue;
    461             }
    462             if (file.endsWith(".bak")) {
    463                 (new File(mDir, file)).delete();
    464                 continue;
    465             }
    466             fileList.add(file);
    467         }
    468         return fileList;
    469     }
    470 
    471     private void checkFileLimitFLOCK() {
    472         // Get all usage stats output files
    473         ArrayList<String> fileList = getUsageStatsFileListFLOCK();
    474         if (fileList == null) {
    475             // Strange but we dont have to delete any thing
    476             return;
    477         }
    478         int count = fileList.size();
    479         if (count <= MAX_NUM_FILES) {
    480             return;
    481         }
    482         // Sort files
    483         Collections.sort(fileList);
    484         count -= MAX_NUM_FILES;
    485         // Delete older files
    486         for (int i = 0; i < count; i++) {
    487             String fileName = fileList.get(i);
    488             File file = new File(mDir, fileName);
    489             Slog.i(TAG, "Deleting usage file : " + fileName);
    490             file.delete();
    491         }
    492     }
    493 
    494     /**
    495      * Conditionally start up a disk write if it's been awhile, or the
    496      * day has rolled over.
    497      *
    498      * This is called indirectly from user-facing actions (when
    499      * 'force' is false) so it tries to be quick, without writing to
    500      * disk directly or acquiring heavy locks.
    501      *
    502      * @params force  do an unconditional, synchronous stats flush
    503      *                to disk on the current thread.
    504      * @params forceWriteHistoryStats Force writing of historical stats.
    505      */
    506     private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) {
    507         int curDay;
    508         synchronized (mCal) {
    509             mCal.setTimeInMillis(System.currentTimeMillis());
    510             curDay = mCal.get(Calendar.DAY_OF_YEAR);
    511         }
    512         final boolean dayChanged = curDay != mLastWriteDay.get();
    513 
    514         // Determine if the day changed...  note that this will be wrong
    515         // if the year has changed but we are in the same day of year...
    516         // we can probably live with this.
    517         final long currElapsedTime = SystemClock.elapsedRealtime();
    518 
    519         // Fast common path, without taking the often-contentious
    520         // mFileLock.
    521         if (!force) {
    522             if (!dayChanged &&
    523                 (currElapsedTime - mLastWriteElapsedTime.get()) < FILE_WRITE_INTERVAL) {
    524                 // wait till the next update
    525                 return;
    526             }
    527             if (mUnforcedDiskWriteRunning.compareAndSet(false, true)) {
    528                 new Thread("UsageStatsService_DiskWriter") {
    529                     public void run() {
    530                         try {
    531                             if (localLOGV) Slog.d(TAG, "Disk writer thread starting.");
    532                             writeStatsToFile(true, false);
    533                         } finally {
    534                             mUnforcedDiskWriteRunning.set(false);
    535                             if (localLOGV) Slog.d(TAG, "Disk writer thread ending.");
    536                         }
    537                     }
    538                 }.start();
    539             }
    540             return;
    541         }
    542 
    543         synchronized (mFileLock) {
    544             // Get the most recent file
    545             mFileLeaf = getCurrentDateStr(FILE_PREFIX);
    546             // Copy current file to back up
    547             File backupFile = null;
    548             if (mFile != null && mFile.exists()) {
    549                 backupFile = new File(mFile.getPath() + ".bak");
    550                 if (!backupFile.exists()) {
    551                     if (!mFile.renameTo(backupFile)) {
    552                         Slog.w(TAG, "Failed to persist new stats");
    553                         return;
    554                     }
    555                 } else {
    556                     mFile.delete();
    557                 }
    558             }
    559 
    560             try {
    561                 // Write mStats to file
    562                 writeStatsFLOCK(mFile);
    563                 mLastWriteElapsedTime.set(currElapsedTime);
    564                 if (dayChanged) {
    565                     mLastWriteDay.set(curDay);
    566                     // clear stats
    567                     synchronized (mStats) {
    568                         mStats.clear();
    569                     }
    570                     mFile = new File(mDir, mFileLeaf);
    571                     checkFileLimitFLOCK();
    572                 }
    573 
    574                 if (dayChanged || forceWriteHistoryStats) {
    575                     // Write history stats daily, or when forced (due to shutdown).
    576                     writeHistoryStatsFLOCK(mHistoryFile);
    577                 }
    578 
    579                 // Delete the backup file
    580                 if (backupFile != null) {
    581                     backupFile.delete();
    582                 }
    583             } catch (IOException e) {
    584                 Slog.w(TAG, "Failed writing stats to file:" + mFile);
    585                 if (backupFile != null) {
    586                     mFile.delete();
    587                     backupFile.renameTo(mFile);
    588                 }
    589             }
    590         }
    591         if (localLOGV) Slog.d(TAG, "Dumped usage stats.");
    592     }
    593 
    594     private void writeStatsFLOCK(File file) throws IOException {
    595         FileOutputStream stream = new FileOutputStream(file);
    596         try {
    597             Parcel out = Parcel.obtain();
    598             writeStatsToParcelFLOCK(out);
    599             stream.write(out.marshall());
    600             out.recycle();
    601             stream.flush();
    602         } finally {
    603             FileUtils.sync(stream);
    604             stream.close();
    605         }
    606     }
    607 
    608     private void writeStatsToParcelFLOCK(Parcel out) {
    609         synchronized (mStatsLock) {
    610             out.writeInt(VERSION);
    611             Set<String> keys = mStats.keySet();
    612             out.writeInt(keys.size());
    613             for (String key : keys) {
    614                 PkgUsageStatsExtended pus = mStats.get(key);
    615                 out.writeString(key);
    616                 pus.writeToParcel(out);
    617             }
    618         }
    619     }
    620 
    621     /** Filter out stats for any packages which aren't present anymore. */
    622     private void filterHistoryStats() {
    623         synchronized (mStatsLock) {
    624             IPackageManager pm = AppGlobals.getPackageManager();
    625             for (int i=0; i<mLastResumeTimes.size(); i++) {
    626                 String pkg = mLastResumeTimes.keyAt(i);
    627                 try {
    628                     if (pm.getPackageUid(pkg, 0) < 0) {
    629                         mLastResumeTimes.removeAt(i);
    630                         i--;
    631                     }
    632                 } catch (RemoteException e) {
    633                 }
    634             }
    635         }
    636     }
    637 
    638     private void writeHistoryStatsFLOCK(AtomicFile historyFile) {
    639         FileOutputStream fos = null;
    640         try {
    641             fos = historyFile.startWrite();
    642             XmlSerializer out = new FastXmlSerializer();
    643             out.setOutput(fos, "utf-8");
    644             out.startDocument(null, true);
    645             out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
    646             out.startTag(null, "usage-history");
    647             synchronized (mStatsLock) {
    648                 for (int i=0; i<mLastResumeTimes.size(); i++) {
    649                     out.startTag(null, "pkg");
    650                     out.attribute(null, "name", mLastResumeTimes.keyAt(i));
    651                     ArrayMap<String, Long> comp = mLastResumeTimes.valueAt(i);
    652                     for (int j=0; j<comp.size(); j++) {
    653                         out.startTag(null, "comp");
    654                         out.attribute(null, "name", comp.keyAt(j));
    655                         out.attribute(null, "lrt", comp.valueAt(j).toString());
    656                         out.endTag(null, "comp");
    657                     }
    658                     out.endTag(null, "pkg");
    659                 }
    660             }
    661             out.endTag(null, "usage-history");
    662             out.endDocument();
    663 
    664             historyFile.finishWrite(fos);
    665         } catch (IOException e) {
    666             Slog.w(TAG,"Error writing history stats" + e);
    667             if (fos != null) {
    668                 historyFile.failWrite(fos);
    669             }
    670         }
    671     }
    672 
    673     public void publish(Context context) {
    674         mContext = context;
    675         ServiceManager.addService(SERVICE_NAME, asBinder());
    676     }
    677 
    678     /**
    679      * Start watching packages to remove stats when a package is uninstalled.
    680      * May only be called when the package manager is ready.
    681      */
    682     public void monitorPackages() {
    683         mPackageMonitor = new PackageMonitor() {
    684             @Override
    685             public void onPackageRemovedAllUsers(String packageName, int uid) {
    686                 synchronized (mStatsLock) {
    687                     mLastResumeTimes.remove(packageName);
    688                 }
    689             }
    690         };
    691         mPackageMonitor.register(mContext, null, true);
    692         filterHistoryStats();
    693     }
    694 
    695     public void shutdown() {
    696         if (mPackageMonitor != null) {
    697             mPackageMonitor.unregister();
    698         }
    699         Slog.i(TAG, "Writing usage stats before shutdown...");
    700         writeStatsToFile(true, true);
    701     }
    702 
    703     public static IUsageStats getService() {
    704         if (sService != null) {
    705             return sService;
    706         }
    707         IBinder b = ServiceManager.getService(SERVICE_NAME);
    708         sService = asInterface(b);
    709         return sService;
    710     }
    711 
    712     public void noteResumeComponent(ComponentName componentName) {
    713         enforceCallingPermission();
    714         String pkgName;
    715         synchronized (mStatsLock) {
    716             if ((componentName == null) ||
    717                     ((pkgName = componentName.getPackageName()) == null)) {
    718                 return;
    719             }
    720 
    721             final boolean samePackage = pkgName.equals(mLastResumedPkg);
    722             if (mIsResumed) {
    723                 if (mLastResumedPkg != null) {
    724                     // We last resumed some other package...  just pause it now
    725                     // to recover.
    726                     if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName
    727                             + " while already resumed in " + mLastResumedPkg);
    728                     PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
    729                     if (pus != null) {
    730                         pus.updatePause();
    731                     }
    732                 }
    733             }
    734 
    735             final boolean sameComp = samePackage
    736                     && componentName.getClassName().equals(mLastResumedComp);
    737 
    738             mIsResumed = true;
    739             mLastResumedPkg = pkgName;
    740             mLastResumedComp = componentName.getClassName();
    741 
    742             if (localLOGV) Slog.i(TAG, "started component:" + pkgName);
    743             PkgUsageStatsExtended pus = mStats.get(pkgName);
    744             if (pus == null) {
    745                 pus = new PkgUsageStatsExtended();
    746                 mStats.put(pkgName, pus);
    747             }
    748             pus.updateResume(mLastResumedComp, !samePackage);
    749             if (!sameComp) {
    750                 pus.addLaunchCount(mLastResumedComp);
    751             }
    752 
    753             ArrayMap<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName);
    754             if (componentResumeTimes == null) {
    755                 componentResumeTimes = new ArrayMap<String, Long>();
    756                 mLastResumeTimes.put(pkgName, componentResumeTimes);
    757             }
    758             componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis());
    759         }
    760     }
    761 
    762     public void notePauseComponent(ComponentName componentName) {
    763         enforceCallingPermission();
    764 
    765         synchronized (mStatsLock) {
    766             String pkgName;
    767             if ((componentName == null) ||
    768                     ((pkgName = componentName.getPackageName()) == null)) {
    769                 return;
    770             }
    771             if (!mIsResumed) {
    772                 if (REPORT_UNEXPECTED) Slog.i(TAG, "Something wrong here, didn't expect "
    773                         + pkgName + " to be paused");
    774                 return;
    775             }
    776             mIsResumed = false;
    777 
    778             if (localLOGV) Slog.i(TAG, "paused component:"+pkgName);
    779 
    780             PkgUsageStatsExtended pus = mStats.get(pkgName);
    781             if (pus == null) {
    782                 // Weird some error here
    783                 Slog.i(TAG, "No package stats for pkg:"+pkgName);
    784                 return;
    785             }
    786             pus.updatePause();
    787         }
    788 
    789         // Persist current data to file if needed.
    790         writeStatsToFile(false, false);
    791     }
    792 
    793     public void noteLaunchTime(ComponentName componentName, int millis) {
    794         enforceCallingPermission();
    795         String pkgName;
    796         if ((componentName == null) ||
    797                 ((pkgName = componentName.getPackageName()) == null)) {
    798             return;
    799         }
    800 
    801         // Persist current data to file if needed.
    802         writeStatsToFile(false, false);
    803 
    804         synchronized (mStatsLock) {
    805             PkgUsageStatsExtended pus = mStats.get(pkgName);
    806             if (pus != null) {
    807                 pus.addLaunchTime(componentName.getClassName(), millis);
    808             }
    809         }
    810     }
    811 
    812     public void noteFullyDrawnTime(ComponentName componentName, int millis) {
    813         enforceCallingPermission();
    814         String pkgName;
    815         if ((componentName == null) ||
    816                 ((pkgName = componentName.getPackageName()) == null)) {
    817             return;
    818         }
    819 
    820         // Persist current data to file if needed.
    821         writeStatsToFile(false, false);
    822 
    823         synchronized (mStatsLock) {
    824             PkgUsageStatsExtended pus = mStats.get(pkgName);
    825             if (pus != null) {
    826                 pus.addFullyDrawnTime(componentName.getClassName(), millis);
    827             }
    828         }
    829     }
    830 
    831     public void enforceCallingPermission() {
    832         if (Binder.getCallingPid() == Process.myPid()) {
    833             return;
    834         }
    835         mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
    836                 Binder.getCallingPid(), Binder.getCallingUid(), null);
    837     }
    838 
    839     public PkgUsageStats getPkgUsageStats(ComponentName componentName) {
    840         mContext.enforceCallingOrSelfPermission(
    841                 android.Manifest.permission.PACKAGE_USAGE_STATS, null);
    842         String pkgName;
    843         if ((componentName == null) ||
    844                 ((pkgName = componentName.getPackageName()) == null)) {
    845             return null;
    846         }
    847         synchronized (mStatsLock) {
    848             PkgUsageStatsExtended pus = mStats.get(pkgName);
    849             Map<String, Long> lastResumeTimes = mLastResumeTimes.get(pkgName);
    850             if (pus == null && lastResumeTimes == null) {
    851                 return null;
    852             }
    853             int launchCount = pus != null ? pus.mLaunchCount : 0;
    854             long usageTime = pus != null ? pus.mUsageTime : 0;
    855             return new PkgUsageStats(pkgName, launchCount, usageTime, lastResumeTimes);
    856         }
    857     }
    858 
    859     public PkgUsageStats[] getAllPkgUsageStats() {
    860         mContext.enforceCallingOrSelfPermission(
    861                 android.Manifest.permission.PACKAGE_USAGE_STATS, null);
    862         synchronized (mStatsLock) {
    863             int size = mLastResumeTimes.size();
    864             if (size <= 0) {
    865                 return null;
    866             }
    867             PkgUsageStats retArr[] = new PkgUsageStats[size];
    868             for (int i=0; i<size; i++) {
    869                 String pkg = mLastResumeTimes.keyAt(i);
    870                 long usageTime = 0;
    871                 int launchCount = 0;
    872 
    873                 PkgUsageStatsExtended pus = mStats.get(pkg);
    874                 if (pus != null) {
    875                     usageTime = pus.mUsageTime;
    876                     launchCount = pus.mLaunchCount;
    877                 }
    878                 retArr[i] = new PkgUsageStats(pkg, launchCount, usageTime,
    879                         mLastResumeTimes.valueAt(i));
    880             }
    881             return retArr;
    882         }
    883     }
    884 
    885     static byte[] readFully(FileInputStream stream) throws java.io.IOException {
    886         int pos = 0;
    887         int avail = stream.available();
    888         byte[] data = new byte[avail];
    889         while (true) {
    890             int amt = stream.read(data, pos, data.length-pos);
    891             if (amt <= 0) {
    892                 return data;
    893             }
    894             pos += amt;
    895             avail = stream.available();
    896             if (avail > data.length-pos) {
    897                 byte[] newData = new byte[pos+avail];
    898                 System.arraycopy(data, 0, newData, 0, pos);
    899                 data = newData;
    900             }
    901         }
    902     }
    903 
    904     private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput,
    905             boolean deleteAfterPrint, HashSet<String> packages) {
    906         List<String> fileList = getUsageStatsFileListFLOCK();
    907         if (fileList == null) {
    908             return;
    909         }
    910         Collections.sort(fileList);
    911         for (String file : fileList) {
    912             if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) {
    913                 // In this mode we don't print the current day's stats, since
    914                 // they are incomplete.
    915                 continue;
    916             }
    917             File dFile = new File(mDir, file);
    918             String dateStr = file.substring(FILE_PREFIX.length());
    919             if (dateStr.length() > 0 && (dateStr.charAt(0) <= '0' || dateStr.charAt(0) >= '9')) {
    920                 // If the remainder does not start with a number, it is not a date,
    921                 // so we should ignore it for purposes here.
    922                 continue;
    923             }
    924             try {
    925                 Parcel in = getParcelForFile(dFile);
    926                 collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput,
    927                         packages);
    928                 if (deleteAfterPrint) {
    929                     // Delete old file after collecting info only for checkin requests
    930                     dFile.delete();
    931                 }
    932             } catch (FileNotFoundException e) {
    933                 Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file);
    934                 return;
    935             } catch (IOException e) {
    936                 Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file);
    937             }
    938         }
    939     }
    940 
    941     private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw,
    942             String date, boolean isCompactOutput, HashSet<String> packages) {
    943         StringBuilder sb = new StringBuilder(512);
    944         if (isCompactOutput) {
    945             sb.append("D:");
    946             sb.append(CHECKIN_VERSION);
    947             sb.append(',');
    948         } else {
    949             sb.append("Date: ");
    950         }
    951 
    952         sb.append(date);
    953 
    954         int vers = in.readInt();
    955         if (vers != VERSION) {
    956             sb.append(" (old data version)");
    957             pw.println(sb.toString());
    958             return;
    959         }
    960 
    961         pw.println(sb.toString());
    962         int N = in.readInt();
    963 
    964         while (N > 0) {
    965             N--;
    966             String pkgName = in.readString();
    967             if (pkgName == null) {
    968                 break;
    969             }
    970             sb.setLength(0);
    971             PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
    972             if (packages != null && !packages.contains(pkgName)) {
    973                 // This package has not been requested -- don't print
    974                 // anything for it.
    975             } else if (isCompactOutput) {
    976                 sb.append("P:");
    977                 sb.append(pkgName);
    978                 sb.append(',');
    979                 sb.append(pus.mLaunchCount);
    980                 sb.append(',');
    981                 sb.append(pus.mUsageTime);
    982                 sb.append('\n');
    983                 final int NLT = pus.mLaunchTimes.size();
    984                 for (int i=0; i<NLT; i++) {
    985                     sb.append("A:");
    986                     String activity = pus.mLaunchTimes.keyAt(i);
    987                     sb.append(activity);
    988                     TimeStats times = pus.mLaunchTimes.valueAt(i);
    989                     sb.append(',');
    990                     sb.append(times.count);
    991                     for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) {
    992                         sb.append(",");
    993                         sb.append(times.times[j]);
    994                     }
    995                     sb.append('\n');
    996                 }
    997                 final int NFDT = pus.mFullyDrawnTimes.size();
    998                 for (int i=0; i<NFDT; i++) {
    999                     sb.append("A:");
   1000                     String activity = pus.mFullyDrawnTimes.keyAt(i);
   1001                     sb.append(activity);
   1002                     TimeStats times = pus.mFullyDrawnTimes.valueAt(i);
   1003                     for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) {
   1004                         sb.append(",");
   1005                         sb.append(times.times[j]);
   1006                     }
   1007                     sb.append('\n');
   1008                 }
   1009 
   1010             } else {
   1011                 sb.append("  ");
   1012                 sb.append(pkgName);
   1013                 sb.append(": ");
   1014                 sb.append(pus.mLaunchCount);
   1015                 sb.append(" times, ");
   1016                 sb.append(pus.mUsageTime);
   1017                 sb.append(" ms");
   1018                 sb.append('\n');
   1019                 final int NLT = pus.mLaunchTimes.size();
   1020                 for (int i=0; i<NLT; i++) {
   1021                     sb.append("    ");
   1022                     sb.append(pus.mLaunchTimes.keyAt(i));
   1023                     TimeStats times = pus.mLaunchTimes.valueAt(i);
   1024                     sb.append(": ");
   1025                     sb.append(times.count);
   1026                     sb.append(" starts");
   1027                     int lastBin = 0;
   1028                     for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) {
   1029                         if (times.times[j] != 0) {
   1030                             sb.append(", ");
   1031                             sb.append(lastBin);
   1032                             sb.append('-');
   1033                             sb.append(LAUNCH_TIME_BINS[j]);
   1034                             sb.append("ms=");
   1035                             sb.append(times.times[j]);
   1036                         }
   1037                         lastBin = LAUNCH_TIME_BINS[j];
   1038                     }
   1039                     if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) {
   1040                         sb.append(", ");
   1041                         sb.append(">=");
   1042                         sb.append(lastBin);
   1043                         sb.append("ms=");
   1044                         sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]);
   1045                     }
   1046                     sb.append('\n');
   1047                 }
   1048                 final int NFDT = pus.mFullyDrawnTimes.size();
   1049                 for (int i=0; i<NFDT; i++) {
   1050                     sb.append("    ");
   1051                     sb.append(pus.mFullyDrawnTimes.keyAt(i));
   1052                     TimeStats times = pus.mFullyDrawnTimes.valueAt(i);
   1053                     sb.append(": fully drawn ");
   1054                     boolean needComma = false;
   1055                     int lastBin = 0;
   1056                     for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) {
   1057                         if (times.times[j] != 0) {
   1058                             if (needComma) {
   1059                                 sb.append(", ");
   1060                             } else {
   1061                                 needComma = true;
   1062                             }
   1063                             sb.append(lastBin);
   1064                             sb.append('-');
   1065                             sb.append(LAUNCH_TIME_BINS[j]);
   1066                             sb.append("ms=");
   1067                             sb.append(times.times[j]);
   1068                         }
   1069                         lastBin = LAUNCH_TIME_BINS[j];
   1070                     }
   1071                     if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) {
   1072                         if (needComma) {
   1073                             sb.append(", ");
   1074                         }
   1075                         sb.append(">=");
   1076                         sb.append(lastBin);
   1077                         sb.append("ms=");
   1078                         sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]);
   1079                     }
   1080                     sb.append('\n');
   1081                 }
   1082             }
   1083 
   1084             pw.write(sb.toString());
   1085         }
   1086     }
   1087 
   1088     /**
   1089      * Searches array of arguments for the specified string
   1090      * @param args array of argument strings
   1091      * @param value value to search for
   1092      * @return true if the value is contained in the array
   1093      */
   1094     private static boolean scanArgs(String[] args, String value) {
   1095         if (args != null) {
   1096             for (String arg : args) {
   1097                 if (value.equals(arg)) {
   1098                     return true;
   1099                 }
   1100             }
   1101         }
   1102         return false;
   1103     }
   1104 
   1105     /**
   1106      * Searches array of arguments for the specified string's data
   1107      * @param args array of argument strings
   1108      * @param value value to search for
   1109      * @return the string of data after the arg, or null if there is none
   1110      */
   1111     private static String scanArgsData(String[] args, String value) {
   1112         if (args != null) {
   1113             final int N = args.length;
   1114             for (int i=0; i<N; i++) {
   1115                 if (value.equals(args[i])) {
   1116                     i++;
   1117                     return i < N ? args[i] : null;
   1118                 }
   1119             }
   1120         }
   1121         return null;
   1122     }
   1123 
   1124     @Override
   1125     /*
   1126      * The data persisted to file is parsed and the stats are computed.
   1127      */
   1128     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   1129         if (mContext.checkCallingPermission(android.Manifest.permission.DUMP)
   1130                 != PackageManager.PERMISSION_GRANTED) {
   1131             pw.println("Permission Denial: can't dump UsageStats from from pid="
   1132                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
   1133                     + " without permission " + android.Manifest.permission.DUMP);
   1134             return;
   1135         }
   1136 
   1137         final boolean isCheckinRequest = scanArgs(args, "--checkin");
   1138         final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c");
   1139         final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d");
   1140         final String rawPackages = scanArgsData(args, "--packages");
   1141 
   1142         // Make sure the current stats are written to the file.  This
   1143         // doesn't need to be done if we are deleting files after printing,
   1144         // since it that case we won't print the current stats.
   1145         if (!deleteAfterPrint) {
   1146             writeStatsToFile(true, false);
   1147         }
   1148 
   1149         HashSet<String> packages = null;
   1150         if (rawPackages != null) {
   1151             if (!"*".equals(rawPackages)) {
   1152                 // A * is a wildcard to show all packages.
   1153                 String[] names = rawPackages.split(",");
   1154                 for (String n : names) {
   1155                     if (packages == null) {
   1156                         packages = new HashSet<String>();
   1157                     }
   1158                     packages.add(n);
   1159                 }
   1160             }
   1161         } else if (isCheckinRequest) {
   1162             // If checkin doesn't specify any packages, then we simply won't
   1163             // show anything.
   1164             Slog.w(TAG, "Checkin without packages");
   1165             return;
   1166         }
   1167 
   1168         synchronized (mFileLock) {
   1169             collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages);
   1170         }
   1171     }
   1172 
   1173 }
   1174