• 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 
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 
TimeStats()109         TimeStats() {
110         }
111 
incCount()112         void incCount() {
113             count++;
114         }
115 
add(int val)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 
TimeStats(Parcel in)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 
writeToParcel(Parcel out)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 
PkgUsageStatsExtended()152         PkgUsageStatsExtended() {
153             mLaunchCount = 0;
154             mUsageTime = 0;
155         }
156 
PkgUsageStatsExtended(Parcel in)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 
updateResume(boolean launched)173         void updateResume(boolean launched) {
174             if (launched) {
175                 mLaunchCount ++;
176             }
177             mResumedTime = SystemClock.elapsedRealtime();
178         }
179 
updatePause()180         void updatePause() {
181             mPausedTime =  SystemClock.elapsedRealtime();
182             mUsageTime += (mPausedTime - mResumedTime);
183         }
184 
addLaunchCount(String comp)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 
addLaunchTime(String comp, int millis)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 
writeToParcel(Parcel out)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 
clear()217         void clear() {
218             mLaunchTimes.clear();
219             mLaunchCount = 0;
220             mUsageTime = 0;
221         }
222     }
223 
UsageStatsService(String dir)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      */
getCurrentDateStr(String prefix)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 
getParcelForFile(File file)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 
readStatsFromFile()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 
readStatsFLOCK(File file)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 
getUsageStatsFileListFLOCK()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 
checkFileLimitFLOCK()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      */
writeStatsToFile(final boolean force)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 
writeStatsFLOCK(File file)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 
writeStatsToParcelFLOCK(Parcel out)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 
publish(Context context)494     public void publish(Context context) {
495         mContext = context;
496         ServiceManager.addService(SERVICE_NAME, asBinder());
497     }
498 
shutdown()499     public void shutdown() {
500         Slog.i(TAG, "Writing usage stats before shutdown...");
501         writeStatsToFile(true);
502     }
503 
getService()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 
noteResumeComponent(ComponentName componentName)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 
notePauseComponent(ComponentName componentName)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 
noteLaunchTime(ComponentName componentName, int millis)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 
enforceCallingPermission()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 
getPkgUsageStats(ComponentName componentName)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 
getAllPkgUsageStats()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 
readFully(FileInputStream stream)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 
collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput, boolean deleteAfterPrint, HashSet<String> packages)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 
collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw, String date, boolean isCompactOutput, HashSet<String> packages)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      */
scanArgs(String[] args, String value)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      */
scanArgsData(String[] args, String value)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      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)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