PageRenderTime 107ms CodeModel.GetById 11ms app.highlight 86ms RepoModel.GetById 1ms app.codeStats 1ms

/services/java/com/android/server/am/UsageStatsService.java

https://github.com/aizuzi/platform_frameworks_base
Java | 1180 lines | 972 code | 103 blank | 105 comment | 223 complexity | 034ce4ccc24dfc9c0133847b51740c8a MD5 | raw file
   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 android.app.AppGlobals;
  20import android.content.ComponentName;
  21import android.content.Context;
  22import android.content.pm.IPackageManager;
  23import android.content.pm.PackageInfo;
  24import android.content.pm.PackageManager;
  25import android.os.Binder;
  26import android.os.IBinder;
  27import android.os.FileUtils;
  28import android.os.Parcel;
  29import android.os.Process;
  30import android.os.RemoteException;
  31import android.os.ServiceManager;
  32import android.os.SystemClock;
  33import android.util.ArrayMap;
  34import android.util.AtomicFile;
  35import android.util.Slog;
  36import android.util.Xml;
  37
  38import com.android.internal.app.IUsageStats;
  39import com.android.internal.content.PackageMonitor;
  40import com.android.internal.os.PkgUsageStats;
  41import com.android.internal.util.FastXmlSerializer;
  42
  43import org.xmlpull.v1.XmlPullParser;
  44import org.xmlpull.v1.XmlPullParserException;
  45import org.xmlpull.v1.XmlSerializer;
  46
  47import java.io.File;
  48import java.io.FileDescriptor;
  49import java.io.FileInputStream;
  50import java.io.FileNotFoundException;
  51import java.io.FileOutputStream;
  52import java.io.IOException;
  53import java.io.PrintWriter;
  54import java.util.ArrayList;
  55import java.util.Calendar;
  56import java.util.Collections;
  57import java.util.HashMap;
  58import java.util.HashSet;
  59import java.util.List;
  60import java.util.Map;
  61import java.util.Set;
  62import java.util.TimeZone;
  63import java.util.concurrent.atomic.AtomicBoolean;
  64import java.util.concurrent.atomic.AtomicInteger;
  65import java.util.concurrent.atomic.AtomicLong;
  66
  67/**
  68 * This service collects the statistics associated with usage
  69 * of various components, like when a particular package is launched or
  70 * paused and aggregates events like number of time a component is launched
  71 * total duration of a component launch.
  72 */
  73public final class UsageStatsService extends IUsageStats.Stub {
  74    public static final String SERVICE_NAME = "usagestats";
  75    private static final boolean localLOGV = false;
  76    private static final boolean REPORT_UNEXPECTED = false;
  77    private static final String TAG = "UsageStats";
  78
  79    // Current on-disk Parcel version
  80    private static final int VERSION = 1008;
  81
  82    private static final int CHECKIN_VERSION = 4;
  83
  84    private static final String FILE_PREFIX = "usage-";
  85
  86    private static final String FILE_HISTORY = FILE_PREFIX + "history.xml";
  87
  88    private static final int FILE_WRITE_INTERVAL = (localLOGV) ? 0 : 30*60*1000; // 30m in ms
  89
  90    private static final int MAX_NUM_FILES = 5;
  91
  92    private static final int NUM_LAUNCH_TIME_BINS = 10;
  93    private static final int[] LAUNCH_TIME_BINS = {
  94        250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000
  95    };
  96
  97    static IUsageStats sService;
  98    private Context mContext;
  99    // structure used to maintain statistics since the last checkin.
 100    final private ArrayMap<String, PkgUsageStatsExtended> mStats
 101            = new ArrayMap<String, PkgUsageStatsExtended>();
 102
 103    // Maintains the last time any component was resumed, for all time.
 104    final private ArrayMap<String, ArrayMap<String, Long>> mLastResumeTimes
 105            = new ArrayMap<String, ArrayMap<String, Long>>();
 106
 107    // To remove last-resume time stats when a pacakge is removed.
 108    private PackageMonitor mPackageMonitor;
 109
 110    // Lock to update package stats. Methods suffixed by SLOCK should invoked with
 111    // this lock held
 112    final Object mStatsLock = new Object();
 113    // Lock to write to file. Methods suffixed by FLOCK should invoked with
 114    // this lock held.
 115    final Object mFileLock = new Object();
 116    // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks
 117    private String mLastResumedPkg;
 118    private String mLastResumedComp;
 119    private boolean mIsResumed;
 120    private File mFile;
 121    private AtomicFile mHistoryFile;
 122    private String mFileLeaf;
 123    private File mDir;
 124
 125    private final Calendar mCal // guarded by itself
 126            = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
 127
 128    private final AtomicInteger mLastWriteDay = new AtomicInteger(-1);
 129    private final AtomicLong mLastWriteElapsedTime = new AtomicLong(0);
 130    private final AtomicBoolean mUnforcedDiskWriteRunning = new AtomicBoolean(false);
 131
 132    static class TimeStats {
 133        int mCount;
 134        final int[] mTimes = new int[NUM_LAUNCH_TIME_BINS];
 135
 136        TimeStats() {
 137        }
 138
 139        void incCount() {
 140            mCount++;
 141        }
 142
 143        void add(int val) {
 144            final int[] bins = LAUNCH_TIME_BINS;
 145            for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
 146                if (val < bins[i]) {
 147                    mTimes[i]++;
 148                    return;
 149                }
 150            }
 151            mTimes[NUM_LAUNCH_TIME_BINS-1]++;
 152        }
 153
 154        TimeStats(Parcel in) {
 155            mCount = in.readInt();
 156            final int[] localTimes = mTimes;
 157            for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
 158                localTimes[i] = in.readInt();
 159            }
 160        }
 161
 162        void writeToParcel(Parcel out) {
 163            out.writeInt(mCount);
 164            final int[] localTimes = mTimes;
 165            for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
 166                out.writeInt(localTimes[i]);
 167            }
 168        }
 169    }
 170
 171    static class PkgUsageStatsExtended {
 172        final ArrayMap<String, TimeStats> mLaunchTimes
 173                = new ArrayMap<String, TimeStats>();
 174        final ArrayMap<String, TimeStats> mFullyDrawnTimes
 175                = new ArrayMap<String, TimeStats>();
 176        int mLaunchCount;
 177        long mUsageTime;
 178        long mPausedTime;
 179        long mResumedTime;
 180
 181        PkgUsageStatsExtended() {
 182            mLaunchCount = 0;
 183            mUsageTime = 0;
 184        }
 185
 186        PkgUsageStatsExtended(Parcel in) {
 187            mLaunchCount = in.readInt();
 188            mUsageTime = in.readLong();
 189            if (localLOGV) Slog.v(TAG, "Launch count: " + mLaunchCount
 190                    + ", Usage time:" + mUsageTime);
 191
 192            final int numLaunchTimeStats = in.readInt();
 193            if (localLOGV) Slog.v(TAG, "Reading launch times: " + numLaunchTimeStats);
 194            mLaunchTimes.ensureCapacity(numLaunchTimeStats);
 195            for (int i=0; i<numLaunchTimeStats; i++) {
 196                String comp = in.readString();
 197                if (localLOGV) Slog.v(TAG, "Component: " + comp);
 198                TimeStats times = new TimeStats(in);
 199                mLaunchTimes.put(comp, times);
 200            }
 201
 202            final int numFullyDrawnTimeStats = in.readInt();
 203            if (localLOGV) Slog.v(TAG, "Reading fully drawn times: " + numFullyDrawnTimeStats);
 204            mFullyDrawnTimes.ensureCapacity(numFullyDrawnTimeStats);
 205            for (int i=0; i<numFullyDrawnTimeStats; i++) {
 206                String comp = in.readString();
 207                if (localLOGV) Slog.v(TAG, "Component: " + comp);
 208                TimeStats times = new TimeStats(in);
 209                mFullyDrawnTimes.put(comp, times);
 210            }
 211        }
 212
 213        void updateResume(String comp, boolean launched) {
 214            if (launched) {
 215                mLaunchCount++;
 216            }
 217            mResumedTime = SystemClock.elapsedRealtime();
 218        }
 219
 220        void updatePause() {
 221            mPausedTime =  SystemClock.elapsedRealtime();
 222            mUsageTime += (mPausedTime - mResumedTime);
 223        }
 224
 225        void addLaunchCount(String comp) {
 226            TimeStats times = mLaunchTimes.get(comp);
 227            if (times == null) {
 228                times = new TimeStats();
 229                mLaunchTimes.put(comp, times);
 230            }
 231            times.incCount();
 232        }
 233
 234        void addLaunchTime(String comp, int millis) {
 235            TimeStats times = mLaunchTimes.get(comp);
 236            if (times == null) {
 237                times = new TimeStats();
 238                mLaunchTimes.put(comp, times);
 239            }
 240            times.add(millis);
 241        }
 242
 243        void addFullyDrawnTime(String comp, int millis) {
 244            TimeStats times = mFullyDrawnTimes.get(comp);
 245            if (times == null) {
 246                times = new TimeStats();
 247                mFullyDrawnTimes.put(comp, times);
 248            }
 249            times.add(millis);
 250        }
 251
 252        void writeToParcel(Parcel out) {
 253            out.writeInt(mLaunchCount);
 254            out.writeLong(mUsageTime);
 255            final int numLaunchTimeStats = mLaunchTimes.size();
 256            out.writeInt(numLaunchTimeStats);
 257            for (int i=0; i<numLaunchTimeStats; i++) {
 258                out.writeString(mLaunchTimes.keyAt(i));
 259                mLaunchTimes.valueAt(i).writeToParcel(out);
 260            }
 261            final int numFullyDrawnTimeStats = mFullyDrawnTimes.size();
 262            out.writeInt(numFullyDrawnTimeStats);
 263            for (int i=0; i<numFullyDrawnTimeStats; i++) {
 264                out.writeString(mFullyDrawnTimes.keyAt(i));
 265                mFullyDrawnTimes.valueAt(i).writeToParcel(out);
 266            }
 267        }
 268
 269        void clear() {
 270            mLaunchTimes.clear();
 271            mFullyDrawnTimes.clear();
 272            mLaunchCount = 0;
 273            mUsageTime = 0;
 274        }
 275    }
 276
 277    UsageStatsService(String dir) {
 278        if (localLOGV) Slog.v(TAG, "UsageStatsService: " + dir);
 279        mDir = new File(dir);
 280        mDir.mkdir();
 281
 282        // Remove any old /data/system/usagestats.* files from previous versions.
 283        File parentDir = mDir.getParentFile();
 284        String files[] = parentDir.list();
 285        if (files != null) {
 286            String prefix = mDir.getName() + ".";
 287            for (String file : files) {
 288                if (file.startsWith(prefix)) {
 289                    Slog.i(TAG, "Deleting old usage file: " + file);
 290                    (new File(parentDir, file)).delete();
 291                }
 292            }
 293        }
 294
 295        // Update current stats which are binned by date
 296        mFileLeaf = getCurrentDateStr(FILE_PREFIX);
 297        mFile = new File(mDir, mFileLeaf);
 298        mHistoryFile = new AtomicFile(new File(mDir, FILE_HISTORY));
 299        readStatsFromFile();
 300        readHistoryStatsFromFile();
 301        mLastWriteElapsedTime.set(SystemClock.elapsedRealtime());
 302        // mCal was set by getCurrentDateStr(), want to use that same time.
 303        mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR));
 304    }
 305
 306    /*
 307     * Utility method to convert date into string.
 308     */
 309    private String getCurrentDateStr(String prefix) {
 310        StringBuilder sb = new StringBuilder();
 311        if (prefix != null) {
 312            sb.append(prefix);
 313        }
 314        synchronized (mCal) {
 315            mCal.setTimeInMillis(System.currentTimeMillis());
 316            sb.append(mCal.get(Calendar.YEAR));
 317            int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1;
 318            if (mm < 10) {
 319                sb.append("0");
 320            }
 321            sb.append(mm);
 322            int dd = mCal.get(Calendar.DAY_OF_MONTH);
 323            if (dd < 10) {
 324                sb.append("0");
 325            }
 326            sb.append(dd);
 327        }
 328        return sb.toString();
 329    }
 330
 331    private Parcel getParcelForFile(File file) throws IOException {
 332        FileInputStream stream = new FileInputStream(file);
 333        try {
 334            byte[] raw = readFully(stream);
 335            Parcel in = Parcel.obtain();
 336            in.unmarshall(raw, 0, raw.length);
 337            in.setDataPosition(0);
 338            return in;
 339        } finally {
 340            stream.close();
 341        }
 342    }
 343
 344    private void readStatsFromFile() {
 345        File newFile = mFile;
 346        synchronized (mFileLock) {
 347            try {
 348                if (newFile.exists()) {
 349                    readStatsFLOCK(newFile);
 350                } else {
 351                    // Check for file limit before creating a new file
 352                    checkFileLimitFLOCK();
 353                    newFile.createNewFile();
 354                }
 355            } catch (IOException e) {
 356                Slog.w(TAG,"Error : " + e + " reading data from file:" + newFile);
 357            }
 358        }
 359    }
 360
 361    private void readStatsFLOCK(File file) throws IOException {
 362        Parcel in = getParcelForFile(file);
 363        int vers = in.readInt();
 364        if (vers != VERSION) {  // vers will be 0 if the parcel file was empty
 365            Slog.w(TAG, "Usage stats version of " + file + " changed from " + vers + " to "
 366                   + VERSION + "; dropping");
 367            return;
 368        }
 369        int N = in.readInt();
 370        while (N > 0) {
 371            N--;
 372            String pkgName = in.readString();
 373            if (pkgName == null) {
 374                break;
 375            }
 376            if (localLOGV) Slog.v(TAG, "Reading package #" + N + ": " + pkgName);
 377            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
 378            synchronized (mStatsLock) {
 379                mStats.put(pkgName, pus);
 380            }
 381        }
 382    }
 383
 384    private void readHistoryStatsFromFile() {
 385        synchronized (mFileLock) {
 386            if (mHistoryFile.getBaseFile().exists()) {
 387                readHistoryStatsFLOCK();
 388            }
 389        }
 390    }
 391
 392    private void readHistoryStatsFLOCK() {
 393        FileInputStream fis = null;
 394        try {
 395            fis = mHistoryFile.openRead();
 396            XmlPullParser parser = Xml.newPullParser();
 397            parser.setInput(fis, null);
 398            int eventType = parser.getEventType();
 399            while (eventType != XmlPullParser.START_TAG &&
 400                    eventType != XmlPullParser.END_DOCUMENT) {
 401                eventType = parser.next();
 402            }
 403            if (eventType == XmlPullParser.END_DOCUMENT) {
 404                return;
 405            }
 406
 407            String tagName = parser.getName();
 408            if ("usage-history".equals(tagName)) {
 409                String pkg = null;
 410                do {
 411                    eventType = parser.next();
 412                    if (eventType == XmlPullParser.START_TAG) {
 413                        tagName = parser.getName();
 414                        int depth = parser.getDepth();
 415                        if ("pkg".equals(tagName) && depth == 2) {
 416                            pkg = parser.getAttributeValue(null, "name");
 417                        } else if ("comp".equals(tagName) && depth == 3 && pkg != null) {
 418                            String comp = parser.getAttributeValue(null, "name");
 419                            String lastResumeTimeStr = parser.getAttributeValue(null, "lrt");
 420                            if (comp != null && lastResumeTimeStr != null) {
 421                                try {
 422                                    long lastResumeTime = Long.parseLong(lastResumeTimeStr);
 423                                    synchronized (mStatsLock) {
 424                                        ArrayMap<String, Long> lrt = mLastResumeTimes.get(pkg);
 425                                        if (lrt == null) {
 426                                            lrt = new ArrayMap<String, Long>();
 427                                            mLastResumeTimes.put(pkg, lrt);
 428                                        }
 429                                        lrt.put(comp, lastResumeTime);
 430                                    }
 431                                } catch (NumberFormatException e) {
 432                                }
 433                            }
 434                        }
 435                    } else if (eventType == XmlPullParser.END_TAG) {
 436                        if ("pkg".equals(parser.getName())) {
 437                            pkg = null;
 438                        }
 439                    }
 440                } while (eventType != XmlPullParser.END_DOCUMENT);
 441            }
 442        } catch (XmlPullParserException e) {
 443            Slog.w(TAG,"Error reading history stats: " + e);
 444        } catch (IOException e) {
 445            Slog.w(TAG,"Error reading history stats: " + e);
 446        } finally {
 447            if (fis != null) {
 448                try {
 449                    fis.close();
 450                } catch (IOException e) {
 451                }
 452            }
 453        }
 454    }
 455
 456    private ArrayList<String> getUsageStatsFileListFLOCK() {
 457        // Check if there are too many files in the system and delete older files
 458        String fList[] = mDir.list();
 459        if (fList == null) {
 460            return null;
 461        }
 462        ArrayList<String> fileList = new ArrayList<String>();
 463        for (String file : fList) {
 464            if (!file.startsWith(FILE_PREFIX)) {
 465                continue;
 466            }
 467            if (file.endsWith(".bak")) {
 468                (new File(mDir, file)).delete();
 469                continue;
 470            }
 471            fileList.add(file);
 472        }
 473        return fileList;
 474    }
 475
 476    private void checkFileLimitFLOCK() {
 477        // Get all usage stats output files
 478        ArrayList<String> fileList = getUsageStatsFileListFLOCK();
 479        if (fileList == null) {
 480            // Empty /data/system/usagestats/ so we don't have anything to delete
 481            return;
 482        }
 483        int count = fileList.size();
 484        if (count <= MAX_NUM_FILES) {
 485            return;
 486        }
 487        // Sort files
 488        Collections.sort(fileList);
 489        count -= MAX_NUM_FILES;
 490        // Delete older files
 491        for (int i = 0; i < count; i++) {
 492            String fileName = fileList.get(i);
 493            File file = new File(mDir, fileName);
 494            Slog.i(TAG, "Deleting usage file : " + fileName);
 495            file.delete();
 496        }
 497    }
 498
 499    /**
 500     * Conditionally start up a disk write if it's been awhile, or the
 501     * day has rolled over.
 502     *
 503     * This is called indirectly from user-facing actions (when
 504     * 'force' is false) so it tries to be quick, without writing to
 505     * disk directly or acquiring heavy locks.
 506     *
 507     * @params force  do an unconditional, synchronous stats flush
 508     *                to disk on the current thread.
 509     * @params forceWriteHistoryStats Force writing of historical stats.
 510     */
 511    private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) {
 512        int curDay;
 513        synchronized (mCal) {
 514            mCal.setTimeInMillis(System.currentTimeMillis());
 515            curDay = mCal.get(Calendar.DAY_OF_YEAR);
 516        }
 517        final boolean dayChanged = curDay != mLastWriteDay.get();
 518
 519        // Determine if the day changed...  note that this will be wrong
 520        // if the year has changed but we are in the same day of year...
 521        // we can probably live with this.
 522        final long currElapsedTime = SystemClock.elapsedRealtime();
 523
 524        // Fast common path, without taking the often-contentious
 525        // mFileLock.
 526        if (!force) {
 527            if (!dayChanged &&
 528                (currElapsedTime - mLastWriteElapsedTime.get()) < FILE_WRITE_INTERVAL) {
 529                // wait till the next update
 530                return;
 531            }
 532            if (mUnforcedDiskWriteRunning.compareAndSet(false, true)) {
 533                new Thread("UsageStatsService_DiskWriter") {
 534                    public void run() {
 535                        try {
 536                            if (localLOGV) Slog.d(TAG, "Disk writer thread starting.");
 537                            writeStatsToFile(true, false);
 538                        } finally {
 539                            mUnforcedDiskWriteRunning.set(false);
 540                            if (localLOGV) Slog.d(TAG, "Disk writer thread ending.");
 541                        }
 542                    }
 543                }.start();
 544            }
 545            return;
 546        }
 547
 548        synchronized (mFileLock) {
 549            // Get the most recent file
 550            mFileLeaf = getCurrentDateStr(FILE_PREFIX);
 551            // Copy current file to back up
 552            File backupFile = null;
 553            if (mFile != null && mFile.exists()) {
 554                backupFile = new File(mFile.getPath() + ".bak");
 555                if (!backupFile.exists()) {
 556                    if (!mFile.renameTo(backupFile)) {
 557                        Slog.w(TAG, "Failed to persist new stats");
 558                        return;
 559                    }
 560                } else {
 561                    mFile.delete();
 562                }
 563            }
 564
 565            try {
 566                // Write mStats to file
 567                writeStatsFLOCK(mFile);
 568                mLastWriteElapsedTime.set(currElapsedTime);
 569                if (dayChanged) {
 570                    mLastWriteDay.set(curDay);
 571                    // clear stats
 572                    synchronized (mStats) {
 573                        mStats.clear();
 574                    }
 575                    mFile = new File(mDir, mFileLeaf);
 576                    checkFileLimitFLOCK();
 577                }
 578
 579                if (dayChanged || forceWriteHistoryStats) {
 580                    // Write history stats daily or when forced (due to shutdown) or when debugging.
 581                    writeHistoryStatsFLOCK();
 582                }
 583
 584                // Delete the backup file
 585                if (backupFile != null) {
 586                    backupFile.delete();
 587                }
 588            } catch (IOException e) {
 589                Slog.w(TAG, "Failed writing stats to file:" + mFile);
 590                if (backupFile != null) {
 591                    mFile.delete();
 592                    backupFile.renameTo(mFile);
 593                }
 594            }
 595        }
 596        if (localLOGV) Slog.d(TAG, "Dumped usage stats.");
 597    }
 598
 599    private void writeStatsFLOCK(File file) throws IOException {
 600        FileOutputStream stream = new FileOutputStream(file);
 601        try {
 602            Parcel out = Parcel.obtain();
 603            writeStatsToParcelFLOCK(out);
 604            stream.write(out.marshall());
 605            out.recycle();
 606            stream.flush();
 607        } finally {
 608            FileUtils.sync(stream);
 609            stream.close();
 610        }
 611    }
 612
 613    private void writeStatsToParcelFLOCK(Parcel out) {
 614        synchronized (mStatsLock) {
 615            out.writeInt(VERSION);
 616            Set<String> keys = mStats.keySet();
 617            out.writeInt(keys.size());
 618            for (String key : keys) {
 619                PkgUsageStatsExtended pus = mStats.get(key);
 620                out.writeString(key);
 621                pus.writeToParcel(out);
 622            }
 623        }
 624    }
 625
 626    /** Filter out stats for any packages which aren't present anymore. */
 627    private void filterHistoryStats() {
 628        synchronized (mStatsLock) {
 629            IPackageManager pm = AppGlobals.getPackageManager();
 630            for (int i=0; i<mLastResumeTimes.size(); i++) {
 631                String pkg = mLastResumeTimes.keyAt(i);
 632                try {
 633                    if (pm.getPackageUid(pkg, 0) < 0) {
 634                        mLastResumeTimes.removeAt(i);
 635                        i--;
 636                    }
 637                } catch (RemoteException e) {
 638                }
 639            }
 640        }
 641    }
 642
 643    private void writeHistoryStatsFLOCK() {
 644        FileOutputStream fos = null;
 645        try {
 646            fos = mHistoryFile.startWrite();
 647            XmlSerializer out = new FastXmlSerializer();
 648            out.setOutput(fos, "utf-8");
 649            out.startDocument(null, true);
 650            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
 651            out.startTag(null, "usage-history");
 652            synchronized (mStatsLock) {
 653                for (int i=0; i<mLastResumeTimes.size(); i++) {
 654                    out.startTag(null, "pkg");
 655                    out.attribute(null, "name", mLastResumeTimes.keyAt(i));
 656                    ArrayMap<String, Long> comp = mLastResumeTimes.valueAt(i);
 657                    for (int j=0; j<comp.size(); j++) {
 658                        out.startTag(null, "comp");
 659                        out.attribute(null, "name", comp.keyAt(j));
 660                        out.attribute(null, "lrt", comp.valueAt(j).toString());
 661                        out.endTag(null, "comp");
 662                    }
 663                    out.endTag(null, "pkg");
 664                }
 665            }
 666            out.endTag(null, "usage-history");
 667            out.endDocument();
 668
 669            mHistoryFile.finishWrite(fos);
 670        } catch (IOException e) {
 671            Slog.w(TAG,"Error writing history stats" + e);
 672            if (fos != null) {
 673                mHistoryFile.failWrite(fos);
 674            }
 675        }
 676    }
 677
 678    public void publish(Context context) {
 679        mContext = context;
 680        ServiceManager.addService(SERVICE_NAME, asBinder());
 681    }
 682
 683    /**
 684     * Start watching packages to remove stats when a package is uninstalled.
 685     * May only be called when the package manager is ready.
 686     */
 687    public void monitorPackages() {
 688        mPackageMonitor = new PackageMonitor() {
 689            @Override
 690            public void onPackageRemovedAllUsers(String packageName, int uid) {
 691                synchronized (mStatsLock) {
 692                    mLastResumeTimes.remove(packageName);
 693                }
 694            }
 695        };
 696        mPackageMonitor.register(mContext, null, true);
 697        filterHistoryStats();
 698    }
 699
 700    public void shutdown() {
 701        if (mPackageMonitor != null) {
 702            mPackageMonitor.unregister();
 703        }
 704        Slog.i(TAG, "Writing usage stats before shutdown...");
 705        writeStatsToFile(true, true);
 706    }
 707
 708    public static IUsageStats getService() {
 709        if (sService != null) {
 710            return sService;
 711        }
 712        IBinder b = ServiceManager.getService(SERVICE_NAME);
 713        sService = asInterface(b);
 714        return sService;
 715    }
 716
 717    @Override
 718    public void noteResumeComponent(ComponentName componentName) {
 719        enforceCallingPermission();
 720        String pkgName;
 721        synchronized (mStatsLock) {
 722            if ((componentName == null) ||
 723                    ((pkgName = componentName.getPackageName()) == null)) {
 724                return;
 725            }
 726
 727            final boolean samePackage = pkgName.equals(mLastResumedPkg);
 728            if (mIsResumed) {
 729                if (mLastResumedPkg != null) {
 730                    // We last resumed some other package...  just pause it now
 731                    // to recover.
 732                    if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName
 733                            + " while already resumed in " + mLastResumedPkg);
 734                    PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
 735                    if (pus != null) {
 736                        pus.updatePause();
 737                    }
 738                }
 739            }
 740
 741            final boolean sameComp = samePackage
 742                    && componentName.getClassName().equals(mLastResumedComp);
 743
 744            mIsResumed = true;
 745            mLastResumedPkg = pkgName;
 746            mLastResumedComp = componentName.getClassName();
 747
 748            if (localLOGV) Slog.i(TAG, "started component:" + pkgName);
 749            PkgUsageStatsExtended pus = mStats.get(pkgName);
 750            if (pus == null) {
 751                pus = new PkgUsageStatsExtended();
 752                mStats.put(pkgName, pus);
 753            }
 754            pus.updateResume(mLastResumedComp, !samePackage);
 755            if (!sameComp) {
 756                pus.addLaunchCount(mLastResumedComp);
 757            }
 758
 759            ArrayMap<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName);
 760            if (componentResumeTimes == null) {
 761                componentResumeTimes = new ArrayMap<String, Long>();
 762                mLastResumeTimes.put(pkgName, componentResumeTimes);
 763            }
 764            componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis());
 765        }
 766    }
 767
 768    @Override
 769    public void notePauseComponent(ComponentName componentName) {
 770        enforceCallingPermission();
 771
 772        synchronized (mStatsLock) {
 773            String pkgName;
 774            if ((componentName == null) ||
 775                    ((pkgName = componentName.getPackageName()) == null)) {
 776                return;
 777            }
 778            if (!mIsResumed) {
 779                if (REPORT_UNEXPECTED) Slog.i(TAG, "Something wrong here, didn't expect "
 780                        + pkgName + " to be paused");
 781                return;
 782            }
 783            mIsResumed = false;
 784
 785            if (localLOGV) Slog.i(TAG, "paused component:"+pkgName);
 786
 787            PkgUsageStatsExtended pus = mStats.get(pkgName);
 788            if (pus == null) {
 789                // Weird some error here
 790                Slog.i(TAG, "No package stats for pkg:"+pkgName);
 791                return;
 792            }
 793            pus.updatePause();
 794        }
 795
 796        // Persist current data to file if needed.
 797        writeStatsToFile(false, false);
 798    }
 799
 800    @Override
 801    public void noteLaunchTime(ComponentName componentName, int millis) {
 802        enforceCallingPermission();
 803        String pkgName;
 804        if ((componentName == null) ||
 805                ((pkgName = componentName.getPackageName()) == null)) {
 806            return;
 807        }
 808
 809        // Persist current data to file if needed.
 810        writeStatsToFile(false, false);
 811
 812        synchronized (mStatsLock) {
 813            PkgUsageStatsExtended pus = mStats.get(pkgName);
 814            if (pus != null) {
 815                pus.addLaunchTime(componentName.getClassName(), millis);
 816            }
 817        }
 818    }
 819
 820    public void noteFullyDrawnTime(ComponentName componentName, int millis) {
 821        enforceCallingPermission();
 822        String pkgName;
 823        if ((componentName == null) ||
 824                ((pkgName = componentName.getPackageName()) == null)) {
 825            return;
 826        }
 827
 828        // Persist current data to file if needed.
 829        writeStatsToFile(false, false);
 830
 831        synchronized (mStatsLock) {
 832            PkgUsageStatsExtended pus = mStats.get(pkgName);
 833            if (pus != null) {
 834                pus.addFullyDrawnTime(componentName.getClassName(), millis);
 835            }
 836        }
 837    }
 838
 839    public void enforceCallingPermission() {
 840        if (Binder.getCallingPid() == Process.myPid()) {
 841            return;
 842        }
 843        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
 844                Binder.getCallingPid(), Binder.getCallingUid(), null);
 845    }
 846
 847    @Override
 848    public PkgUsageStats getPkgUsageStats(ComponentName componentName) {
 849        mContext.enforceCallingOrSelfPermission(
 850                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
 851        String pkgName;
 852        if ((componentName == null) ||
 853                ((pkgName = componentName.getPackageName()) == null)) {
 854            return null;
 855        }
 856        synchronized (mStatsLock) {
 857            PkgUsageStatsExtended pus = mStats.get(pkgName);
 858            Map<String, Long> lastResumeTimes = mLastResumeTimes.get(pkgName);
 859            if (pus == null && lastResumeTimes == null) {
 860                return null;
 861            }
 862            int launchCount = pus != null ? pus.mLaunchCount : 0;
 863            long usageTime = pus != null ? pus.mUsageTime : 0;
 864            return new PkgUsageStats(pkgName, launchCount, usageTime, lastResumeTimes);
 865        }
 866    }
 867
 868    @Override
 869    public PkgUsageStats[] getAllPkgUsageStats() {
 870        mContext.enforceCallingOrSelfPermission(
 871                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
 872        synchronized (mStatsLock) {
 873            int size = mLastResumeTimes.size();
 874            if (size <= 0) {
 875                return null;
 876            }
 877            PkgUsageStats retArr[] = new PkgUsageStats[size];
 878            for (int i=0; i<size; i++) {
 879                String pkg = mLastResumeTimes.keyAt(i);
 880                long usageTime = 0;
 881                int launchCount = 0;
 882
 883                PkgUsageStatsExtended pus = mStats.get(pkg);
 884                if (pus != null) {
 885                    usageTime = pus.mUsageTime;
 886                    launchCount = pus.mLaunchCount;
 887                }
 888                retArr[i] = new PkgUsageStats(pkg, launchCount, usageTime,
 889                        mLastResumeTimes.valueAt(i));
 890            }
 891            return retArr;
 892        }
 893    }
 894
 895    static byte[] readFully(FileInputStream stream) throws IOException {
 896        int pos = 0;
 897        int avail = stream.available();
 898        byte[] data = new byte[avail];
 899        while (true) {
 900            int amt = stream.read(data, pos, data.length-pos);
 901            if (amt <= 0) {
 902                return data;
 903            }
 904            pos += amt;
 905            avail = stream.available();
 906            if (avail > data.length-pos) {
 907                byte[] newData = new byte[pos+avail];
 908                System.arraycopy(data, 0, newData, 0, pos);
 909                data = newData;
 910            }
 911        }
 912    }
 913
 914    private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput,
 915            boolean deleteAfterPrint, HashSet<String> packages) {
 916        List<String> fileList = getUsageStatsFileListFLOCK();
 917        if (fileList == null) {
 918            return;
 919        }
 920        Collections.sort(fileList);
 921        for (String file : fileList) {
 922            if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) {
 923                // In this mode we don't print the current day's stats, since
 924                // they are incomplete.
 925                continue;
 926            }
 927            File dFile = new File(mDir, file);
 928            String dateStr = file.substring(FILE_PREFIX.length());
 929            if (dateStr.length() > 0 && (dateStr.charAt(0) <= '0' || dateStr.charAt(0) >= '9')) {
 930                // If the remainder does not start with a number, it is not a date,
 931                // so we should ignore it for purposes here.
 932                continue;
 933            }
 934            try {
 935                Parcel in = getParcelForFile(dFile);
 936                collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput,
 937                        packages);
 938                if (deleteAfterPrint) {
 939                    // Delete old file after collecting info only for checkin requests
 940                    dFile.delete();
 941                }
 942            } catch (IOException e) {
 943                Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file);
 944            }
 945        }
 946    }
 947
 948    private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw,
 949            String date, boolean isCompactOutput, HashSet<String> packages) {
 950        StringBuilder sb = new StringBuilder(512);
 951        if (isCompactOutput) {
 952            sb.append("D:");
 953            sb.append(CHECKIN_VERSION);
 954            sb.append(',');
 955        } else {
 956            sb.append("Date: ");
 957        }
 958
 959        sb.append(date);
 960
 961        int vers = in.readInt();
 962        if (vers != VERSION) {
 963            sb.append(" (old data version)");
 964            pw.println(sb.toString());
 965            return;
 966        }
 967
 968        pw.println(sb.toString());
 969        int N = in.readInt();
 970
 971        while (N > 0) {
 972            N--;
 973            String pkgName = in.readString();
 974            if (pkgName == null) {
 975                break;
 976            }
 977            sb.setLength(0);
 978            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
 979            if (packages != null && !packages.contains(pkgName)) {
 980                // This package has not been requested -- don't print
 981                // anything for it.
 982            } else if (isCompactOutput) {
 983                sb.append("P:");
 984                sb.append(pkgName);
 985                sb.append(',');
 986                sb.append(pus.mLaunchCount);
 987                sb.append(',');
 988                sb.append(pus.mUsageTime);
 989                sb.append('\n');
 990                final int NLT = pus.mLaunchTimes.size();
 991                for (int i=0; i<NLT; i++) {
 992                    sb.append("A:");
 993                    String activity = pus.mLaunchTimes.keyAt(i);
 994                    sb.append(activity);
 995                    TimeStats times = pus.mLaunchTimes.valueAt(i);
 996                    sb.append(',');
 997                    sb.append(times.mCount);
 998                    for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) {
 999                        sb.append(",");
1000                        sb.append(times.mTimes[j]);
1001                    }
1002                    sb.append('\n');
1003                }
1004                final int NFDT = pus.mFullyDrawnTimes.size();
1005                for (int i=0; i<NFDT; i++) {
1006                    sb.append("A:");
1007                    String activity = pus.mFullyDrawnTimes.keyAt(i);
1008                    sb.append(activity);
1009                    TimeStats times = pus.mFullyDrawnTimes.valueAt(i);
1010                    for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) {
1011                        sb.append(",");
1012                        sb.append(times.mTimes[j]);
1013                    }
1014                    sb.append('\n');
1015                }
1016
1017            } else {
1018                sb.append("  ");
1019                sb.append(pkgName);
1020                sb.append(": ");
1021                sb.append(pus.mLaunchCount);
1022                sb.append(" times, ");
1023                sb.append(pus.mUsageTime);
1024                sb.append(" ms");
1025                sb.append('\n');
1026                final int NLT = pus.mLaunchTimes.size();
1027                for (int i=0; i<NLT; i++) {
1028                    sb.append("    ");
1029                    sb.append(pus.mLaunchTimes.keyAt(i));
1030                    TimeStats times = pus.mLaunchTimes.valueAt(i);
1031                    sb.append(": ");
1032                    sb.append(times.mCount);
1033                    sb.append(" starts");
1034                    int lastBin = 0;
1035                    for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) {
1036                        if (times.mTimes[j] != 0) {
1037                            sb.append(", ");
1038                            sb.append(lastBin);
1039                            sb.append('-');
1040                            sb.append(LAUNCH_TIME_BINS[j]);
1041                            sb.append("ms=");
1042                            sb.append(times.mTimes[j]);
1043                        }
1044                        lastBin = LAUNCH_TIME_BINS[j];
1045                    }
1046                    if (times.mTimes[NUM_LAUNCH_TIME_BINS-1] != 0) {
1047                        sb.append(", ");
1048                        sb.append(">=");
1049                        sb.append(lastBin);
1050                        sb.append("ms=");
1051                        sb.append(times.mTimes[NUM_LAUNCH_TIME_BINS-1]);
1052                    }
1053                    sb.append('\n');
1054                }
1055                final int NFDT = pus.mFullyDrawnTimes.size();
1056                for (int i=0; i<NFDT; i++) {
1057                    sb.append("    ");
1058                    sb.append(pus.mFullyDrawnTimes.keyAt(i));
1059                    TimeStats times = pus.mFullyDrawnTimes.valueAt(i);
1060                    sb.append(": fully drawn ");
1061                    boolean needComma = false;
1062                    int lastBin = 0;
1063                    for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) {
1064                        if (times.mTimes[j] != 0) {
1065                            if (needComma) {
1066                                sb.append(", ");
1067                            } else {
1068                                needComma = true;
1069                            }
1070                            sb.append(lastBin);
1071                            sb.append('-');
1072                            sb.append(LAUNCH_TIME_BINS[j]);
1073                            sb.append("ms=");
1074                            sb.append(times.mTimes[j]);
1075                        }
1076                        lastBin = LAUNCH_TIME_BINS[j];
1077                    }
1078                    if (times.mTimes[NUM_LAUNCH_TIME_BINS-1] != 0) {
1079                        if (needComma) {
1080                            sb.append(", ");
1081                        }
1082                        sb.append(">=");
1083                        sb.append(lastBin);
1084                        sb.append("ms=");
1085                        sb.append(times.mTimes[NUM_LAUNCH_TIME_BINS-1]);
1086                    }
1087                    sb.append('\n');
1088                }
1089            }
1090
1091            pw.write(sb.toString());
1092        }
1093    }
1094
1095    /**
1096     * Searches array of arguments for the specified string
1097     * @param args array of argument strings
1098     * @param value value to search for
1099     * @return true if the value is contained in the array
1100     */
1101    private static boolean scanArgs(String[] args, String value) {
1102        if (args != null) {
1103            for (String arg : args) {
1104                if (value.equals(arg)) {
1105                    return true;
1106                }
1107            }
1108        }
1109        return false;
1110    }
1111
1112    /**
1113     * Searches array of arguments for the specified string's data
1114     * @param args array of argument strings
1115     * @param value value to search for
1116     * @return the string of data after the arg, or null if there is none
1117     */
1118    private static String scanArgsData(String[] args, String value) {
1119        if (args != null) {
1120            final int N = args.length;
1121            for (int i=0; i<N; i++) {
1122                if (value.equals(args[i])) {
1123                    i++;
1124                    return i < N ? args[i] : null;
1125                }
1126            }
1127        }
1128        return null;
1129    }
1130
1131    /*
1132     * The data persisted to file is parsed and the stats are computed.
1133     */
1134    @Override
1135    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1136        if (mContext.checkCallingPermission(android.Manifest.permission.DUMP)
1137                != PackageManager.PERMISSION_GRANTED) {
1138            pw.println("Permission Denial: can't dump UsageStats from from pid="
1139                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
1140                    + " without permission " + android.Manifest.permission.DUMP);
1141            return;
1142        }
1143
1144        final boolean isCheckinRequest = scanArgs(args, "--checkin");
1145        final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c");
1146        final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d");
1147        final String rawPackages = scanArgsData(args, "--packages");
1148
1149        // Make sure the current stats are written to the file.  This
1150        // doesn't need to be done if we are deleting files after printing,
1151        // since in that case we won't print the current stats.
1152        if (!deleteAfterPrint) {
1153            writeStatsToFile(true, false);
1154        }
1155
1156        HashSet<String> packages = null;
1157        if (rawPackages != null) {
1158            if (!"*".equals(rawPackages)) {
1159                // A * is a wildcard to show all packages.
1160                String[] names = rawPackages.split(",");
1161                if (names.length != 0) {
1162                    packages = new HashSet<String>();
1163                }
1164                for (String n : names) {
1165                    packages.add(n);
1166                }
1167            }
1168        } else if (isCheckinRequest) {
1169            // If checkin doesn't specify any packages, then we simply won't
1170            // show anything.
1171            Slog.w(TAG, "Checkin without packages");
1172            return;
1173        }
1174
1175        synchronized (mFileLock) {
1176            collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages);
1177        }
1178    }
1179
1180}