UsageStatsService.java revision be1c422a73cf70e1478a13463de5ab929991c619
19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/*
29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2006-2007 The Android Open Source Project
39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License.
69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at
79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project *
109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and
149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License.
159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage com.android.server.am;
189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport com.android.internal.app.IUsageStats;
209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
213e7b3c0f270d2bf86fb701a94e55cb135480d571Yu Shan Emily Lauimport android.content.ComponentName;
229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.content.Context;
239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.Binder;
249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.IBinder;
259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport com.android.internal.os.PkgUsageStats;
269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.Parcel;
279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.Process;
289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.ServiceManager;
299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.SystemClock;
309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.util.Log;
319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.File;
329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileDescriptor;
339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileInputStream;
349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileNotFoundException;
359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.FileOutputStream;
369066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.IOException;
379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.io.PrintWriter;
389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.ArrayList;
399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Calendar;
409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Collections;
419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.HashMap;
429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.HashSet;
439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.List;
449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Map;
459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Set;
469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.TimeZone;
479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/**
499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * This service collects the statistics associated with usage
509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * of various components, like when a particular package is launched or
519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * paused and aggregates events like number of time a component is launched
529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * total duration of a component launch.
539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */
549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpublic final class UsageStatsService extends IUsageStats.Stub {
559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    public static final String SERVICE_NAME = "usagestats";
569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final boolean localLOGV = false;
579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String TAG = "UsageStats";
589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    // Current on-disk Parcel version
609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final int VERSION = 1005;
619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
623e7b3c0f270d2bf86fb701a94e55cb135480d571Yu Shan Emily Lau    private static final int CHECKIN_VERSION = 4;
639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final String FILE_PREFIX = "usage-";
659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms
679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final int MAX_NUM_FILES = 5;
699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project
709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    private static final int NUM_LAUNCH_TIME_BINS = 10;
719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project    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) 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
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                    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     */
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                Log.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            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
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            Log.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 (!mFile.renameTo(backupFile)) {
387                    Log.w(TAG, "Failed to persist new stats");
388                    return;
389                }
390            }
391
392            try {
393                // Write mStats to file
394                writeStatsFLOCK();
395                mLastWriteElapsedTime = currElapsedTime;
396                if (dayChanged) {
397                    mLastWriteDay = curDay;
398                    // clear stats
399                    synchronized (mStats) {
400                        mStats.clear();
401                    }
402                    mFile = new File(mDir, mFileLeaf);
403                    checkFileLimitFLOCK();
404                }
405                // Delete the backup file
406                if (backupFile != null) {
407                    backupFile.delete();
408                }
409            } catch (IOException e) {
410                Log.w(TAG, "Failed writing stats to file:" + mFile);
411                if (backupFile != null) {
412                    mFile.delete();
413                    backupFile.renameTo(mFile);
414                }
415            }
416        }
417    }
418
419    private void writeStatsFLOCK() throws IOException {
420        FileOutputStream stream = new FileOutputStream(mFile);
421        try {
422            Parcel out = Parcel.obtain();
423            writeStatsToParcelFLOCK(out);
424            stream.write(out.marshall());
425            out.recycle();
426            stream.flush();
427        } finally {
428            stream.close();
429        }
430    }
431
432    private void writeStatsToParcelFLOCK(Parcel out) {
433        synchronized (mStatsLock) {
434            out.writeInt(VERSION);
435            Set<String> keys = mStats.keySet();
436            out.writeInt(keys.size());
437            for (String key : keys) {
438                PkgUsageStatsExtended pus = mStats.get(key);
439                out.writeString(key);
440                pus.writeToParcel(out);
441            }
442        }
443    }
444
445    public void publish(Context context) {
446        mContext = context;
447        ServiceManager.addService(SERVICE_NAME, asBinder());
448    }
449
450    public void shutdown() {
451        Log.w(TAG, "Writing usage stats before shutdown...");
452        writeStatsToFile(true);
453    }
454
455    public static IUsageStats getService() {
456        if (sService != null) {
457            return sService;
458        }
459        IBinder b = ServiceManager.getService(SERVICE_NAME);
460        sService = asInterface(b);
461        return sService;
462    }
463
464    public void noteResumeComponent(ComponentName componentName) {
465        enforceCallingPermission();
466        String pkgName;
467        synchronized (mStatsLock) {
468            if ((componentName == null) ||
469                    ((pkgName = componentName.getPackageName()) == null)) {
470                return;
471            }
472
473            final boolean samePackage = pkgName.equals(mLastResumedPkg);
474            if (mIsResumed) {
475                if (mLastResumedPkg != null) {
476                    // We last resumed some other package...  just pause it now
477                    // to recover.
478                    Log.i(TAG, "Unexpected resume of " + pkgName
479                            + " while already resumed in " + mLastResumedPkg);
480                    PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
481                    if (pus != null) {
482                        pus.updatePause();
483                    }
484                }
485            }
486
487            final boolean sameComp = samePackage
488                    && componentName.getClassName().equals(mLastResumedComp);
489
490            mIsResumed = true;
491            mLastResumedPkg = pkgName;
492            mLastResumedComp = componentName.getClassName();
493
494            if (localLOGV) Log.i(TAG, "started component:" + pkgName);
495            PkgUsageStatsExtended pus = mStats.get(pkgName);
496            if (pus == null) {
497                pus = new PkgUsageStatsExtended();
498                mStats.put(pkgName, pus);
499            }
500            pus.updateResume(!samePackage);
501            if (!sameComp) {
502                pus.addLaunchCount(mLastResumedComp);
503            }
504        }
505    }
506
507    public void notePauseComponent(ComponentName componentName) {
508        enforceCallingPermission();
509
510        synchronized (mStatsLock) {
511            String pkgName;
512            if ((componentName == null) ||
513                    ((pkgName = componentName.getPackageName()) == null)) {
514                return;
515            }
516            if (!mIsResumed) {
517                Log.i(TAG, "Something wrong here, didn't expect "
518                        + pkgName + " to be paused");
519                return;
520            }
521            mIsResumed = false;
522
523            if (localLOGV) Log.i(TAG, "paused component:"+pkgName);
524
525            PkgUsageStatsExtended pus = mStats.get(pkgName);
526            if (pus == null) {
527                // Weird some error here
528                Log.i(TAG, "No package stats for pkg:"+pkgName);
529                return;
530            }
531            pus.updatePause();
532        }
533
534        // Persist current data to file if needed.
535        writeStatsToFile(false);
536    }
537
538    public void noteLaunchTime(ComponentName componentName, int millis) {
539        enforceCallingPermission();
540        String pkgName;
541        if ((componentName == null) ||
542                ((pkgName = componentName.getPackageName()) == null)) {
543            return;
544        }
545
546        // Persist current data to file if needed.
547        writeStatsToFile(false);
548
549        synchronized (mStatsLock) {
550            PkgUsageStatsExtended pus = mStats.get(pkgName);
551            if (pus != null) {
552                pus.addLaunchTime(componentName.getClassName(), millis);
553            }
554        }
555    }
556
557    public void enforceCallingPermission() {
558        if (Binder.getCallingPid() == Process.myPid()) {
559            return;
560        }
561        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
562                Binder.getCallingPid(), Binder.getCallingUid(), null);
563    }
564
565    public PkgUsageStats getPkgUsageStats(ComponentName componentName) {
566        mContext.enforceCallingOrSelfPermission(
567                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
568        String pkgName;
569        if ((componentName == null) ||
570                ((pkgName = componentName.getPackageName()) == null)) {
571            return null;
572        }
573        synchronized (mStatsLock) {
574            PkgUsageStatsExtended pus = mStats.get(pkgName);
575            if (pus == null) {
576               return null;
577            }
578            return new PkgUsageStats(pkgName, pus.mLaunchCount, pus.mUsageTime);
579        }
580    }
581
582    public PkgUsageStats[] getAllPkgUsageStats() {
583        mContext.enforceCallingOrSelfPermission(
584                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
585        synchronized (mStatsLock) {
586            Set<String> keys = mStats.keySet();
587            int size = keys.size();
588            if (size <= 0) {
589                return null;
590            }
591            PkgUsageStats retArr[] = new PkgUsageStats[size];
592            int i = 0;
593            for (String key: keys) {
594                PkgUsageStatsExtended pus = mStats.get(key);
595                retArr[i] = new PkgUsageStats(key, pus.mLaunchCount, pus.mUsageTime);
596                i++;
597            }
598            return retArr;
599        }
600    }
601
602    static byte[] readFully(FileInputStream stream) throws java.io.IOException {
603        int pos = 0;
604        int avail = stream.available();
605        byte[] data = new byte[avail];
606        while (true) {
607            int amt = stream.read(data, pos, data.length-pos);
608            if (amt <= 0) {
609                return data;
610            }
611            pos += amt;
612            avail = stream.available();
613            if (avail > data.length-pos) {
614                byte[] newData = new byte[pos+avail];
615                System.arraycopy(data, 0, newData, 0, pos);
616                data = newData;
617            }
618        }
619    }
620
621    private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput,
622            boolean deleteAfterPrint, HashSet<String> packages) {
623        List<String> fileList = getUsageStatsFileListFLOCK();
624        if (fileList == null) {
625            return;
626        }
627        Collections.sort(fileList);
628        for (String file : fileList) {
629            if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) {
630                // In this mode we don't print the current day's stats, since
631                // they are incomplete.
632                continue;
633            }
634            File dFile = new File(mDir, file);
635            String dateStr = file.substring(FILE_PREFIX.length());
636            try {
637                Parcel in = getParcelForFile(dFile);
638                collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput,
639                        packages);
640                if (deleteAfterPrint) {
641                    // Delete old file after collecting info only for checkin requests
642                    dFile.delete();
643                }
644            } catch (FileNotFoundException e) {
645                Log.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file);
646                return;
647            } catch (IOException e) {
648                Log.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file);
649            }
650        }
651    }
652
653    private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw,
654            String date, boolean isCompactOutput, HashSet<String> packages) {
655        StringBuilder sb = new StringBuilder(512);
656        if (isCompactOutput) {
657            sb.append("D:");
658            sb.append(CHECKIN_VERSION);
659            sb.append(',');
660        } else {
661            sb.append("Date: ");
662        }
663
664        sb.append(date);
665
666        int vers = in.readInt();
667        if (vers != VERSION) {
668            sb.append(" (old data version)");
669            pw.println(sb.toString());
670            return;
671        }
672
673        pw.println(sb.toString());
674        int N = in.readInt();
675
676        while (N > 0) {
677            N--;
678            String pkgName = in.readString();
679            if (pkgName == null) {
680                break;
681            }
682            sb.setLength(0);
683            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
684            if (packages != null && !packages.contains(pkgName)) {
685                // This package has not been requested -- don't print
686                // anything for it.
687            } else if (isCompactOutput) {
688                sb.append("P:");
689                sb.append(pkgName);
690                sb.append(',');
691                sb.append(pus.mLaunchCount);
692                sb.append(',');
693                sb.append(pus.mUsageTime);
694                sb.append('\n');
695                final int NC = pus.mLaunchTimes.size();
696                if (NC > 0) {
697                    for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) {
698                        sb.append("A:");
699                        String activity = ent.getKey();
700                        if (activity.startsWith(pkgName)) {
701                            sb.append('*');
702                            sb.append(activity.substring(
703                                    pkgName.length(), activity.length()));
704                        } else {
705                            sb.append(activity);
706                        }
707                        TimeStats times = ent.getValue();
708                        sb.append(',');
709                        sb.append(times.count);
710                        for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
711                            sb.append(",");
712                            sb.append(times.times[i]);
713                        }
714                        sb.append('\n');
715                    }
716                }
717
718            } else {
719                sb.append("  ");
720                sb.append(pkgName);
721                sb.append(": ");
722                sb.append(pus.mLaunchCount);
723                sb.append(" times, ");
724                sb.append(pus.mUsageTime);
725                sb.append(" ms");
726                sb.append('\n');
727                final int NC = pus.mLaunchTimes.size();
728                if (NC > 0) {
729                    for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) {
730                        sb.append("    ");
731                        sb.append(ent.getKey());
732                        TimeStats times = ent.getValue();
733                        sb.append(": ");
734                        sb.append(times.count);
735                        sb.append(" starts");
736                        int lastBin = 0;
737                        for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
738                            if (times.times[i] != 0) {
739                                sb.append(", ");
740                                sb.append(lastBin);
741                                sb.append('-');
742                                sb.append(LAUNCH_TIME_BINS[i]);
743                                sb.append("ms=");
744                                sb.append(times.times[i]);
745                            }
746                            lastBin = LAUNCH_TIME_BINS[i];
747                        }
748                        if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) {
749                            sb.append(", ");
750                            sb.append(">=");
751                            sb.append(lastBin);
752                            sb.append("ms=");
753                            sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]);
754                        }
755                        sb.append('\n');
756                    }
757                }
758            }
759
760            pw.write(sb.toString());
761        }
762    }
763
764    /**
765     * Searches array of arguments for the specified string
766     * @param args array of argument strings
767     * @param value value to search for
768     * @return true if the value is contained in the array
769     */
770    private static boolean scanArgs(String[] args, String value) {
771        if (args != null) {
772            for (String arg : args) {
773                if (value.equals(arg)) {
774                    return true;
775                }
776            }
777        }
778        return false;
779    }
780
781    /**
782     * Searches array of arguments for the specified string's data
783     * @param args array of argument strings
784     * @param value value to search for
785     * @return the string of data after the arg, or null if there is none
786     */
787    private static String scanArgsData(String[] args, String value) {
788        if (args != null) {
789            final int N = args.length;
790            for (int i=0; i<N; i++) {
791                if (value.equals(args[i])) {
792                    i++;
793                    return i < N ? args[i] : null;
794                }
795            }
796        }
797        return null;
798    }
799
800    @Override
801    /*
802     * The data persisted to file is parsed and the stats are computed.
803     */
804    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
805        final boolean isCheckinRequest = scanArgs(args, "--checkin");
806        final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c");
807        final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d");
808        final String rawPackages = scanArgsData(args, "--packages");
809
810        // Make sure the current stats are written to the file.  This
811        // doesn't need to be done if we are deleting files after printing,
812        // since it that case we won't print the current stats.
813        if (!deleteAfterPrint) {
814            writeStatsToFile(true);
815        }
816
817        HashSet<String> packages = null;
818        if (rawPackages != null) {
819            if (!"*".equals(rawPackages)) {
820                // A * is a wildcard to show all packages.
821                String[] names = rawPackages.split(",");
822                for (String n : names) {
823                    if (packages == null) {
824                        packages = new HashSet<String>();
825                    }
826                    packages.add(n);
827                }
828            }
829        } else if (isCheckinRequest) {
830            // If checkin doesn't specify any packages, then we simply won't
831            // show anything.
832            Log.w(TAG, "Checkin without packages");
833            return;
834        }
835
836        synchronized (mFileLock) {
837            collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages);
838        }
839    }
840
841}
842