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