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