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
17package com.android.server.am;
18
19import com.android.internal.app.IUsageStats;
20
21import android.content.ComponentName;
22import android.content.Context;
23import android.os.Binder;
24import android.os.IBinder;
25import com.android.internal.os.PkgUsageStats;
26import android.os.Parcel;
27import android.os.Process;
28import android.os.ServiceManager;
29import android.os.SystemClock;
30import android.util.Slog;
31import java.io.File;
32import java.io.FileDescriptor;
33import java.io.FileInputStream;
34import java.io.FileNotFoundException;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.io.PrintWriter;
38import java.util.ArrayList;
39import java.util.Calendar;
40import java.util.Collections;
41import java.util.HashMap;
42import java.util.HashSet;
43import java.util.List;
44import java.util.Map;
45import java.util.Set;
46import 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 */
54public 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
101        TimeStats() {
102        }
103
104        void incCount() {
105            count++;
106        }
107
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
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
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
144        PkgUsageStatsExtended() {
145            mLaunchCount = 0;
146            mUsageTime = 0;
147        }
148
149        PkgUsageStatsExtended(Parcel in) {
150            mLaunchCount = in.readInt();
151            mUsageTime = in.readLong();
152            if (localLOGV) Slog.v(TAG, "Launch count: " + mLaunchCount
153                    + ", Usage time:" + mUsageTime);
154
155            final int N = in.readInt();
156            if (localLOGV) Slog.v(TAG, "Reading comps: " + N);
157            for (int i=0; i<N; i++) {
158                String comp = in.readString();
159                if (localLOGV) Slog.v(TAG, "Component: " + comp);
160                TimeStats times = new TimeStats(in);
161                mLaunchTimes.put(comp, times);
162            }
163        }
164
165        void updateResume(boolean launched) {
166            if (launched) {
167                mLaunchCount ++;
168            }
169            mResumedTime = SystemClock.elapsedRealtime();
170        }
171
172        void updatePause() {
173            mPausedTime =  SystemClock.elapsedRealtime();
174            mUsageTime += (mPausedTime - mResumedTime);
175        }
176
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
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
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
209        void clear() {
210            mLaunchTimes.clear();
211            mLaunchCount = 0;
212            mUsageTime = 0;
213        }
214    }
215
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                    Slog.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     */
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
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
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                Slog.w(TAG,"Error : " + e + " reading data from file:" + newFile);
295            }
296        }
297    }
298
299    private void readStatsFLOCK(File file) throws IOException {
300        Parcel in = getParcelForFile(file);
301        int vers = in.readInt();
302        if (vers != VERSION) {
303            Slog.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) Slog.v(TAG, "Reading package #" + N + ": " + pkgName);
314            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
315            synchronized (mStatsLock) {
316                mStats.put(pkgName, pus);
317            }
318        }
319    }
320
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
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            Slog.i(TAG, "Deleting usage file : " + fileName);
360            file.delete();
361        }
362    }
363
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 = null;
384            if (mFile != null && mFile.exists()) {
385                backupFile = new File(mFile.getPath() + ".bak");
386                if (!backupFile.exists()) {
387                    if (!mFile.renameTo(backupFile)) {
388                        Slog.w(TAG, "Failed to persist new stats");
389                        return;
390                    }
391                } else {
392                    mFile.delete();
393                }
394            }
395
396            try {
397                // Write mStats to file
398                writeStatsFLOCK();
399                mLastWriteElapsedTime = currElapsedTime;
400                if (dayChanged) {
401                    mLastWriteDay = curDay;
402                    // clear stats
403                    synchronized (mStats) {
404                        mStats.clear();
405                    }
406                    mFile = new File(mDir, mFileLeaf);
407                    checkFileLimitFLOCK();
408                }
409                // Delete the backup file
410                if (backupFile != null) {
411                    backupFile.delete();
412                }
413            } catch (IOException e) {
414                Slog.w(TAG, "Failed writing stats to file:" + mFile);
415                if (backupFile != null) {
416                    mFile.delete();
417                    backupFile.renameTo(mFile);
418                }
419            }
420        }
421    }
422
423    private void writeStatsFLOCK() throws IOException {
424        FileOutputStream stream = new FileOutputStream(mFile);
425        try {
426            Parcel out = Parcel.obtain();
427            writeStatsToParcelFLOCK(out);
428            stream.write(out.marshall());
429            out.recycle();
430            stream.flush();
431        } finally {
432            stream.close();
433        }
434    }
435
436    private void writeStatsToParcelFLOCK(Parcel out) {
437        synchronized (mStatsLock) {
438            out.writeInt(VERSION);
439            Set<String> keys = mStats.keySet();
440            out.writeInt(keys.size());
441            for (String key : keys) {
442                PkgUsageStatsExtended pus = mStats.get(key);
443                out.writeString(key);
444                pus.writeToParcel(out);
445            }
446        }
447    }
448
449    public void publish(Context context) {
450        mContext = context;
451        ServiceManager.addService(SERVICE_NAME, asBinder());
452    }
453
454    public void shutdown() {
455        Slog.w(TAG, "Writing usage stats before shutdown...");
456        writeStatsToFile(true);
457    }
458
459    public static IUsageStats getService() {
460        if (sService != null) {
461            return sService;
462        }
463        IBinder b = ServiceManager.getService(SERVICE_NAME);
464        sService = asInterface(b);
465        return sService;
466    }
467
468    public void noteResumeComponent(ComponentName componentName) {
469        enforceCallingPermission();
470        String pkgName;
471        synchronized (mStatsLock) {
472            if ((componentName == null) ||
473                    ((pkgName = componentName.getPackageName()) == null)) {
474                return;
475            }
476
477            final boolean samePackage = pkgName.equals(mLastResumedPkg);
478            if (mIsResumed) {
479                if (mLastResumedPkg != null) {
480                    // We last resumed some other package...  just pause it now
481                    // to recover.
482                    Slog.i(TAG, "Unexpected resume of " + pkgName
483                            + " while already resumed in " + mLastResumedPkg);
484                    PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
485                    if (pus != null) {
486                        pus.updatePause();
487                    }
488                }
489            }
490
491            final boolean sameComp = samePackage
492                    && componentName.getClassName().equals(mLastResumedComp);
493
494            mIsResumed = true;
495            mLastResumedPkg = pkgName;
496            mLastResumedComp = componentName.getClassName();
497
498            if (localLOGV) Slog.i(TAG, "started component:" + pkgName);
499            PkgUsageStatsExtended pus = mStats.get(pkgName);
500            if (pus == null) {
501                pus = new PkgUsageStatsExtended();
502                mStats.put(pkgName, pus);
503            }
504            pus.updateResume(!samePackage);
505            if (!sameComp) {
506                pus.addLaunchCount(mLastResumedComp);
507            }
508        }
509    }
510
511    public void notePauseComponent(ComponentName componentName) {
512        enforceCallingPermission();
513
514        synchronized (mStatsLock) {
515            String pkgName;
516            if ((componentName == null) ||
517                    ((pkgName = componentName.getPackageName()) == null)) {
518                return;
519            }
520            if (!mIsResumed) {
521                Slog.i(TAG, "Something wrong here, didn't expect "
522                        + pkgName + " to be paused");
523                return;
524            }
525            mIsResumed = false;
526
527            if (localLOGV) Slog.i(TAG, "paused component:"+pkgName);
528
529            PkgUsageStatsExtended pus = mStats.get(pkgName);
530            if (pus == null) {
531                // Weird some error here
532                Slog.i(TAG, "No package stats for pkg:"+pkgName);
533                return;
534            }
535            pus.updatePause();
536        }
537
538        // Persist current data to file if needed.
539        writeStatsToFile(false);
540    }
541
542    public void noteLaunchTime(ComponentName componentName, int millis) {
543        enforceCallingPermission();
544        String pkgName;
545        if ((componentName == null) ||
546                ((pkgName = componentName.getPackageName()) == null)) {
547            return;
548        }
549
550        // Persist current data to file if needed.
551        writeStatsToFile(false);
552
553        synchronized (mStatsLock) {
554            PkgUsageStatsExtended pus = mStats.get(pkgName);
555            if (pus != null) {
556                pus.addLaunchTime(componentName.getClassName(), millis);
557            }
558        }
559    }
560
561    public void enforceCallingPermission() {
562        if (Binder.getCallingPid() == Process.myPid()) {
563            return;
564        }
565        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
566                Binder.getCallingPid(), Binder.getCallingUid(), null);
567    }
568
569    public PkgUsageStats getPkgUsageStats(ComponentName componentName) {
570        mContext.enforceCallingOrSelfPermission(
571                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
572        String pkgName;
573        if ((componentName == null) ||
574                ((pkgName = componentName.getPackageName()) == null)) {
575            return null;
576        }
577        synchronized (mStatsLock) {
578            PkgUsageStatsExtended pus = mStats.get(pkgName);
579            if (pus == null) {
580               return null;
581            }
582            return new PkgUsageStats(pkgName, pus.mLaunchCount, pus.mUsageTime);
583        }
584    }
585
586    public PkgUsageStats[] getAllPkgUsageStats() {
587        mContext.enforceCallingOrSelfPermission(
588                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
589        synchronized (mStatsLock) {
590            Set<String> keys = mStats.keySet();
591            int size = keys.size();
592            if (size <= 0) {
593                return null;
594            }
595            PkgUsageStats retArr[] = new PkgUsageStats[size];
596            int i = 0;
597            for (String key: keys) {
598                PkgUsageStatsExtended pus = mStats.get(key);
599                retArr[i] = new PkgUsageStats(key, pus.mLaunchCount, pus.mUsageTime);
600                i++;
601            }
602            return retArr;
603        }
604    }
605
606    static byte[] readFully(FileInputStream stream) throws java.io.IOException {
607        int pos = 0;
608        int avail = stream.available();
609        byte[] data = new byte[avail];
610        while (true) {
611            int amt = stream.read(data, pos, data.length-pos);
612            if (amt <= 0) {
613                return data;
614            }
615            pos += amt;
616            avail = stream.available();
617            if (avail > data.length-pos) {
618                byte[] newData = new byte[pos+avail];
619                System.arraycopy(data, 0, newData, 0, pos);
620                data = newData;
621            }
622        }
623    }
624
625    private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput,
626            boolean deleteAfterPrint, HashSet<String> packages) {
627        List<String> fileList = getUsageStatsFileListFLOCK();
628        if (fileList == null) {
629            return;
630        }
631        Collections.sort(fileList);
632        for (String file : fileList) {
633            if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) {
634                // In this mode we don't print the current day's stats, since
635                // they are incomplete.
636                continue;
637            }
638            File dFile = new File(mDir, file);
639            String dateStr = file.substring(FILE_PREFIX.length());
640            try {
641                Parcel in = getParcelForFile(dFile);
642                collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput,
643                        packages);
644                if (deleteAfterPrint) {
645                    // Delete old file after collecting info only for checkin requests
646                    dFile.delete();
647                }
648            } catch (FileNotFoundException e) {
649                Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file);
650                return;
651            } catch (IOException e) {
652                Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file);
653            }
654        }
655    }
656
657    private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw,
658            String date, boolean isCompactOutput, HashSet<String> packages) {
659        StringBuilder sb = new StringBuilder(512);
660        if (isCompactOutput) {
661            sb.append("D:");
662            sb.append(CHECKIN_VERSION);
663            sb.append(',');
664        } else {
665            sb.append("Date: ");
666        }
667
668        sb.append(date);
669
670        int vers = in.readInt();
671        if (vers != VERSION) {
672            sb.append(" (old data version)");
673            pw.println(sb.toString());
674            return;
675        }
676
677        pw.println(sb.toString());
678        int N = in.readInt();
679
680        while (N > 0) {
681            N--;
682            String pkgName = in.readString();
683            if (pkgName == null) {
684                break;
685            }
686            sb.setLength(0);
687            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
688            if (packages != null && !packages.contains(pkgName)) {
689                // This package has not been requested -- don't print
690                // anything for it.
691            } else if (isCompactOutput) {
692                sb.append("P:");
693                sb.append(pkgName);
694                sb.append(',');
695                sb.append(pus.mLaunchCount);
696                sb.append(',');
697                sb.append(pus.mUsageTime);
698                sb.append('\n');
699                final int NC = pus.mLaunchTimes.size();
700                if (NC > 0) {
701                    for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) {
702                        sb.append("A:");
703                        String activity = ent.getKey();
704                        if (activity.startsWith(pkgName)) {
705                            sb.append('*');
706                            sb.append(activity.substring(
707                                    pkgName.length(), activity.length()));
708                        } else {
709                            sb.append(activity);
710                        }
711                        TimeStats times = ent.getValue();
712                        sb.append(',');
713                        sb.append(times.count);
714                        for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
715                            sb.append(",");
716                            sb.append(times.times[i]);
717                        }
718                        sb.append('\n');
719                    }
720                }
721
722            } else {
723                sb.append("  ");
724                sb.append(pkgName);
725                sb.append(": ");
726                sb.append(pus.mLaunchCount);
727                sb.append(" times, ");
728                sb.append(pus.mUsageTime);
729                sb.append(" ms");
730                sb.append('\n');
731                final int NC = pus.mLaunchTimes.size();
732                if (NC > 0) {
733                    for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) {
734                        sb.append("    ");
735                        sb.append(ent.getKey());
736                        TimeStats times = ent.getValue();
737                        sb.append(": ");
738                        sb.append(times.count);
739                        sb.append(" starts");
740                        int lastBin = 0;
741                        for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
742                            if (times.times[i] != 0) {
743                                sb.append(", ");
744                                sb.append(lastBin);
745                                sb.append('-');
746                                sb.append(LAUNCH_TIME_BINS[i]);
747                                sb.append("ms=");
748                                sb.append(times.times[i]);
749                            }
750                            lastBin = LAUNCH_TIME_BINS[i];
751                        }
752                        if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) {
753                            sb.append(", ");
754                            sb.append(">=");
755                            sb.append(lastBin);
756                            sb.append("ms=");
757                            sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]);
758                        }
759                        sb.append('\n');
760                    }
761                }
762            }
763
764            pw.write(sb.toString());
765        }
766    }
767
768    /**
769     * Searches array of arguments for the specified string
770     * @param args array of argument strings
771     * @param value value to search for
772     * @return true if the value is contained in the array
773     */
774    private static boolean scanArgs(String[] args, String value) {
775        if (args != null) {
776            for (String arg : args) {
777                if (value.equals(arg)) {
778                    return true;
779                }
780            }
781        }
782        return false;
783    }
784
785    /**
786     * Searches array of arguments for the specified string's data
787     * @param args array of argument strings
788     * @param value value to search for
789     * @return the string of data after the arg, or null if there is none
790     */
791    private static String scanArgsData(String[] args, String value) {
792        if (args != null) {
793            final int N = args.length;
794            for (int i=0; i<N; i++) {
795                if (value.equals(args[i])) {
796                    i++;
797                    return i < N ? args[i] : null;
798                }
799            }
800        }
801        return null;
802    }
803
804    @Override
805    /*
806     * The data persisted to file is parsed and the stats are computed.
807     */
808    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
809        final boolean isCheckinRequest = scanArgs(args, "--checkin");
810        final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c");
811        final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d");
812        final String rawPackages = scanArgsData(args, "--packages");
813
814        // Make sure the current stats are written to the file.  This
815        // doesn't need to be done if we are deleting files after printing,
816        // since it that case we won't print the current stats.
817        if (!deleteAfterPrint) {
818            writeStatsToFile(true);
819        }
820
821        HashSet<String> packages = null;
822        if (rawPackages != null) {
823            if (!"*".equals(rawPackages)) {
824                // A * is a wildcard to show all packages.
825                String[] names = rawPackages.split(",");
826                for (String n : names) {
827                    if (packages == null) {
828                        packages = new HashSet<String>();
829                    }
830                    packages.add(n);
831                }
832            }
833        } else if (isCheckinRequest) {
834            // If checkin doesn't specify any packages, then we simply won't
835            // show anything.
836            Slog.w(TAG, "Checkin without packages");
837            return;
838        }
839
840        synchronized (mFileLock) {
841            collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages);
842        }
843    }
844
845}
846