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