Merge branch 'readonly-p4-donut' into donut
diff --git a/api/current.xml b/api/current.xml
index 26a416d..6704766 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -28346,6 +28346,28 @@
  visibility="public"
 >
 </field>
+<field name="ACTION_POWER_CONNECTED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.intent.action.ACTION_POWER_CONNECTED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_POWER_DISCONNECTED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.intent.action.ACTION_POWER_DISCONNECTED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="ACTION_PROVIDER_CHANGED"
  type="java.lang.String"
  transient="false"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 849a37d..9b1f0f9 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -611,7 +611,7 @@
     private IBinder mToken;
     /*package*/ String mEmbeddedID;
     private Application mApplication;
-    private Intent mIntent;
+    /*package*/ Intent mIntent;
     private ComponentName mComponent;
     /*package*/ ActivityInfo mActivityInfo;
     /*package*/ ActivityThread mMainThread;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d816193..09862d2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1689,7 +1689,7 @@
 
                     r.packageInfo = getPackageInfoNoCheck(
                             r.activityInfo.applicationInfo);
-                    handleLaunchActivity(r);
+                    handleLaunchActivity(r, null);
                 } break;
                 case RELAUNCH_ACTIVITY: {
                     ActivityRecord r = (ActivityRecord)msg.obj;
@@ -2109,7 +2109,7 @@
                     + ", comp=" + name
                     + ", token=" + token);
         }
-        return performLaunchActivity(r);
+        return performLaunchActivity(r, null);
     }
 
     public final Activity getActivity(IBinder token) {
@@ -2159,7 +2159,7 @@
         queueOrSendMessage(H.CLEAN_UP_CONTEXT, cci);
     }
 
-    private final Activity performLaunchActivity(ActivityRecord r) {
+    private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent) {
         // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
 
         ActivityInfo aInfo = r.activityInfo;
@@ -2219,6 +2219,9 @@
                         r.lastNonConfigurationInstance, r.lastNonConfigurationChildInstances,
                         config);
                 
+                if (customIntent != null) {
+                    activity.mIntent = customIntent;
+                }
                 r.lastNonConfigurationInstance = null;
                 r.lastNonConfigurationChildInstances = null;
                 activity.mStartedActivity = false;
@@ -2274,14 +2277,14 @@
         return activity;
     }
 
-    private final void handleLaunchActivity(ActivityRecord r) {
+    private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) {
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
         unscheduleGcIdler();
 
         if (localLOGV) Log.v(
             TAG, "Handling launch of " + r);
-        Activity a = performLaunchActivity(r);
+        Activity a = performLaunchActivity(r, customIntent);
 
         if (a != null) {
             handleResumeActivity(r.token, false, r.isForward);
@@ -3243,6 +3246,7 @@
         }
         
         r.activity.mConfigChangeFlags |= configChanges;
+        Intent currentIntent = r.activity.mIntent;
         
         Bundle savedState = null;
         if (!r.paused) {
@@ -3275,7 +3279,7 @@
             r.state = savedState;
         }
         
-        handleLaunchActivity(r);
+        handleLaunchActivity(r, currentIntent);
     }
 
     private final void handleRequestThumbnail(IBinder token) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 99cf34c..e82a86c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -508,6 +508,8 @@
  *     <li> {@link #ACTION_PACKAGE_DATA_CLEARED}
  *     <li> {@link #ACTION_UID_REMOVED}
  *     <li> {@link #ACTION_BATTERY_CHANGED}
+ *     <li> {@link #ACTION_POWER_CONNECTED}
+ *     <li> {@link #ACTION_POWER_DISCONNECTED} 
  * </ul>
  *
  * <h3>Standard Categories</h3>
@@ -1250,6 +1252,24 @@
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
     /**
+     * Broadcast Action:  External power has been connected to the device.
+     * This is intended for applications that wish to register specifically to this notification.
+     * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
+     * stay active to receive this notification.  This action can be used to implement actions
+     * that wait until power is available to trigger.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_POWER_CONNECTED = "android.intent.action.ACTION_POWER_CONNECTED";
+    /**
+     * Broadcast Action:  External power has been removed from the device.
+     * This is intended for applications that wish to register specifically to this notification.
+     * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
+     * stay active to receive this notification.  This action can be used to implement actions
+     * that wait until power is available to trigger. 
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED";    
+    /**
      * Broadcast Action:  Indicates low memory condition on the device
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index 9f07c0a..66eefb2 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -30,6 +30,9 @@
  */
 final public class Proxy {
 
+    // Set to true to enable extra debugging.
+    static final private boolean DEBUG = false;
+
     static final public String PROXY_CHANGE_ACTION =
         "android.intent.action.PROXY_CHANGE";
 
@@ -49,7 +52,7 @@
         if (host != null) {
             int i = host.indexOf(':');
             if (i == -1) {
-                if (android.util.Config.DEBUG) {
+                if (DEBUG) {
                     Assert.assertTrue(host.length() == 0);
                 }
                 return null;
@@ -73,12 +76,12 @@
         if (host != null) {
             int i = host.indexOf(':');
             if (i == -1) {
-                if (android.util.Config.DEBUG) {
+                if (DEBUG) {
                     Assert.assertTrue(host.length() == 0);
                 }
                 return -1;
             }
-            if (android.util.Config.DEBUG) {
+            if (DEBUG) {
                 Assert.assertTrue(i < host.length());
             }
             return Integer.parseInt(host.substring(i+1));
diff --git a/core/java/android/provider/Gmail.java b/core/java/android/provider/Gmail.java
index cc03968..c4b29ae 100644
--- a/core/java/android/provider/Gmail.java
+++ b/core/java/android/provider/Gmail.java
@@ -38,7 +38,6 @@
 import android.text.TextUtils.SimpleStringSplitter;
 import android.text.style.CharacterStyle;
 import android.text.util.Regex;
-import android.util.Config;
 import android.util.Log;
 
 import java.io.UnsupportedEncodingException;
@@ -61,6 +60,9 @@
  * @hide
  */
 public final class Gmail {
+    // Set to true to enable extra debugging.
+    private static final boolean DEBUG = false;
+
     public static final String GMAIL_AUTH_SERVICE = "mail";
     // These constants come from google3/java/com/google/caribou/backend/MailLabel.java.
     public static final String LABEL_SENT = "^f";
@@ -1195,7 +1197,7 @@
 
         @Override
         public void onChange(boolean selfChange) {
-            if (Config.DEBUG) {
+            if (DEBUG) {
                 Log.d(TAG, "MailCursor is notifying " + mObservers.size() + " observers");
             }
             for (MailCursorObserver o: mObservers) {
diff --git a/core/java/com/android/internal/app/IUsageStats.aidl b/core/java/com/android/internal/app/IUsageStats.aidl
index 6b053d5..1ea7409 100755
--- a/core/java/com/android/internal/app/IUsageStats.aidl
+++ b/core/java/com/android/internal/app/IUsageStats.aidl
@@ -22,6 +22,7 @@
 interface IUsageStats {
     void noteResumeComponent(in ComponentName componentName);
     void notePauseComponent(in ComponentName componentName);
+    void noteLaunchTime(in ComponentName componentName, int millis);
     PkgUsageStats getPkgUsageStats(in ComponentName componentName);
     PkgUsageStats[] getAllPkgUsageStats();
 }
diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java
index 7cd6b17..90d8c9d 100644
--- a/services/java/com/android/server/BatteryService.java
+++ b/services/java/com/android/server/BatteryService.java
@@ -84,7 +84,7 @@
     private static final int CRITICAL_BATTERY_LEVEL = 4; 
 
     private static final int DUMP_MAX_LENGTH = 24 * 1024;
-    private static final String[] DUMPSYS_ARGS = new String[] { "-c", "-u" };
+    private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "-u" };
     private static final String BATTERY_STATS_SERVICE_NAME = "batteryinfo";
     
     private static final String DUMPSYS_DATA_PATH = "/data/system/";
@@ -247,6 +247,20 @@
                 logOutlier = true;
             }
             
+            // Separate broadcast is sent for power connected / not connected
+            // since the standard intent will not wake any applications and some
+            // applications may want to have smart behavior based on this.
+            if (mPlugType != 0 && mLastPlugType == 0) {
+                Intent intent = new Intent(Intent.ACTION_POWER_CONNECTED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                mContext.sendBroadcast(intent);
+            }
+            else if (mPlugType == 0 && mLastPlugType != 0) {
+                Intent intent = new Intent(Intent.ACTION_POWER_DISCONNECTED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                mContext.sendBroadcast(intent);
+            }
+            
             mLastBatteryStatus = mBatteryStatus;
             mLastBatteryHealth = mBatteryHealth;
             mLastBatteryPresent = mBatteryPresent;
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 0b1ddc8..b0fcb1c 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -77,7 +77,6 @@
 import android.os.SystemProperties;
 import android.os.TokenWatcher;
 import android.provider.Settings;
-import android.util.Config;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -137,7 +136,7 @@
     
     static final boolean PROFILE_ORIENTATION = false;
     static final boolean BLUR = true;
-    static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+    static final boolean localLOGV = DEBUG;
     
     static final int LOG_WM_NO_SURFACE_MEMORY = 31000;
     
@@ -2023,7 +2022,7 @@
             wtoken.appFullscreen = fullscreen;
             wtoken.requestedOrientation = requestedOrientation;
             mAppTokens.add(addPos, wtoken);
-            if (Config.LOGV) Log.v(TAG, "Adding new app token: " + wtoken);
+            if (localLOGV) Log.v(TAG, "Adding new app token: " + wtoken);
             mTokenMap.put(token.asBinder(), wtoken);
             mTokenList.add(wtoken);
             
@@ -4801,14 +4800,11 @@
                     mPaused = true;
                 } else {
                     if (mLastWin == null) {
-                        if (Config.LOGI) Log.i(
-                            TAG, "Key dispatching not paused: no last window.");
+                        Log.i(TAG, "Key dispatching not paused: no last window.");
                     } else if (mFinished) {
-                        if (Config.LOGI) Log.i(
-                            TAG, "Key dispatching not paused: finished last key.");
+                        Log.i(TAG, "Key dispatching not paused: finished last key.");
                     } else {
-                        if (Config.LOGI) Log.i(
-                            TAG, "Key dispatching not paused: window in higher layer.");
+                        Log.i(TAG, "Key dispatching not paused: window in higher layer.");
                     }
                 }
                 */
@@ -7423,7 +7419,7 @@
     private boolean mInLayout = false;
     private final void performLayoutAndPlaceSurfacesLocked() {
         if (mInLayout) {
-            if (Config.DEBUG) {
+            if (DEBUG) {
                 throw new RuntimeException("Recursive call!");
             }
             Log.w(TAG, "performLayoutAndPlaceSurfacesLocked called while in layout");
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index d676c00..59aa29f 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -1270,7 +1270,7 @@
         mBatteryStatsService.getActiveStatistics().writeLocked();
         
         mUsageStatsService = new UsageStatsService( new File(
-                systemDir, "usagestats.bin").toString());
+                systemDir, "usagestats").toString());
 
         mConfiguration.makeDefault();
         mProcessStats.init();
@@ -8401,7 +8401,7 @@
 
     private static final void dumpApplicationMemoryUsage(FileDescriptor fd,
             PrintWriter pw, List list, String prefix, String[] args) {
-        final boolean isCheckinRequest = scanArgs(args, "-c");
+        final boolean isCheckinRequest = scanArgs(args, "--checkin");
         long uptime = SystemClock.uptimeMillis();
         long realtime = SystemClock.elapsedRealtime();
         
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index c5907af..e265f43 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -295,7 +295,7 @@
             boolean isCheckin = false;
             if (args != null) {
                 for (String arg : args) {
-                    if ("-c".equals(arg)) {
+                    if ("--checkin".equals(arg)) {
                         isCheckin = true;
                         break;
                     }
diff --git a/services/java/com/android/server/am/HistoryRecord.java b/services/java/com/android/server/am/HistoryRecord.java
index 0f62471..ae16b14 100644
--- a/services/java/com/android/server/am/HistoryRecord.java
+++ b/services/java/com/android/server/am/HistoryRecord.java
@@ -335,14 +335,18 @@
     
     public void windowsVisible() {
         synchronized(service) {
-            if (ActivityManagerService.SHOW_ACTIVITY_START_TIME
-                    && startTime != 0) {
+            if (startTime != 0) {
                 long time = SystemClock.uptimeMillis() - startTime;
-                EventLog.writeEvent(ActivityManagerService.LOG_ACTIVITY_LAUNCH_TIME,
-                        System.identityHashCode(this), shortComponentName, time);
-                Log.i(ActivityManagerService.TAG, "Displayed activity "
-                        + shortComponentName
-                        + ": " + time + " ms");
+                if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {
+                    EventLog.writeEvent(ActivityManagerService.LOG_ACTIVITY_LAUNCH_TIME,
+                            System.identityHashCode(this), shortComponentName, time);
+                    Log.i(ActivityManagerService.TAG, "Displayed activity "
+                            + shortComponentName
+                            + ": " + time + " ms");
+                }
+                if (time > 0) {
+                    service.mUsageStatsService.noteLaunchTime(realActivity, (int)time);
+                }
                 startTime = 0;
             }
             if (ActivityManagerService.DEBUG_SWITCH) Log.v(
diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java
index 3922f39..7709efd 100755
--- a/services/java/com/android/server/am/UsageStatsService.java
+++ b/services/java/com/android/server/am/UsageStatsService.java
@@ -37,11 +37,11 @@
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TimeZone;
 
 /**
  * This service collects the statistics associated with usage
@@ -53,6 +53,20 @@
     public static final String SERVICE_NAME = "usagestats";
     private static final boolean localLOGV = false;
     private static final String TAG = "UsageStats";
+    
+    // Current on-disk Parcel version
+    private static final int VERSION = 1003;
+
+    private static final int CHECKIN_VERSION = 2;
+    
+    private static final String FILE_PREFIX = "usage-";
+    
+    private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms
+    
+    private static final int MAX_NUM_FILES = 5;
+    
+    private static final int MAX_LAUNCH_TIME_SAMPLES = 50;
+    
     static IUsageStats sService;
     private Context mContext;
     // structure used to maintain statistics since the last checkin.
@@ -66,16 +80,76 @@
     // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks
     private String mResumedPkg;
     private File mFile;
+    private String mFileLeaf;
     //private File mBackupFile;
-    private long mLastWriteRealTime;
-    private int _FILE_WRITE_INTERVAL = 30*60*1000; //ms
-    private static final String _PREFIX_DELIMIT=".";
-    private String mFilePrefix;
+    private long mLastWriteElapsedTime;
+    private File mDir;
     private Calendar mCal;
-    private static final int  _MAX_NUM_FILES = 10;
-    private long mLastTime;
+    private int mLastWriteDay;
+    
+    static class TimeStats {
+        int count;
+        
+        boolean haveStats;
+        
+        int samples;
+        int minimum;
+        int maximum;
+        int average;
+        int median;
+        
+        int size;
+        int[] array;
+        
+        private int avail;
+        
+        void add(int val) {
+            count++;
+            if (size > MAX_LAUNCH_TIME_SAMPLES) {
+                return;
+            }
+            if (size >= avail) {
+                avail = ((avail+2)*3)/2;
+                int[] newarray = new int[avail];
+                if (array != null) {
+                    System.arraycopy(array, 0, newarray, 0, size);
+                }
+                array = newarray;
+            }
+            array[size] = val;
+            size++;
+            haveStats = false;
+        }
+        
+        void computeStats() {
+            if (haveStats) {
+                return;
+            }
+            
+            average = 0;
+            int i = samples = size;
+            if (i > 0) {
+                java.util.Arrays.sort(array, 0, i);
+                i--;
+                minimum = maximum = average = array[i];
+                median = array[i/2];
+                while (i > 0) {
+                    i--;
+                    int v = array[i];
+                    if (v < minimum) minimum = v;
+                    if (v > maximum) maximum = v;
+                    average += v;
+                }
+                average = average/size;
+            } else {
+                minimum = maximum = median = 0;
+            }
+        }
+    }
     
     private class PkgUsageStatsExtended {
+        final HashMap<String, TimeStats> mLaunchTimes
+                = new HashMap<String, TimeStats>();
         int mLaunchCount;
         long mUsageTime;
         long mPausedTime;
@@ -85,44 +159,142 @@
             mLaunchCount = 0;
             mUsageTime = 0;
         }
+        
+        PkgUsageStatsExtended(Parcel in) {
+            mLaunchCount = in.readInt();
+            mUsageTime = in.readLong();
+            if (localLOGV) Log.v(TAG, "Launch count: " + mLaunchCount
+                    + ", Usage time:" + mUsageTime);
+            
+            final int N = in.readInt();
+            if (localLOGV) Log.v(TAG, "Reading comps: " + N);
+            for (int i=0; i<N; i++) {
+                String comp = in.readString();
+                final int M = in.readInt();
+                final int count = in.readInt();
+                if (localLOGV) Log.v(TAG, "Component: " + comp + ", times: " + M);
+                if (M > 0) {
+                    TimeStats times = new TimeStats();
+                    times.count = count;
+                    times.size = times.avail = M;
+                    times.array = new int[M];
+                    for (int j=0; j<M; j++) {
+                        times.array[j] = in.readInt();
+                    }
+                    mLaunchTimes.put(comp, times);
+                } else if (M == -1) {
+                    TimeStats times = new TimeStats();
+                    times.count = count;
+                    times.samples = in.readInt();
+                    times.minimum = in.readInt();
+                    times.maximum = in.readInt();
+                    times.average = in.readInt();
+                    times.median = in.readInt();
+                    times.haveStats = true;
+                    mLaunchTimes.put(comp, times);
+                }
+            }
+        }
+        
         void updateResume() {
             mLaunchCount ++;
             mResumedTime = SystemClock.elapsedRealtime();
         }
+        
         void updatePause() {
             mPausedTime =  SystemClock.elapsedRealtime();
             mUsageTime += (mPausedTime - mResumedTime);
         }
+        
+        void addLaunchTime(String comp, int millis) {
+            TimeStats times = mLaunchTimes.get(comp);
+            if (times == null) {
+                times = new TimeStats();
+                mLaunchTimes.put(comp, times);
+            }
+            times.add(millis);
+        }
+        
+        void writeToParcel(Parcel out, boolean allTimes) {
+            out.writeInt(mLaunchCount);
+            out.writeLong(mUsageTime);
+            final int N = mLaunchTimes.size();
+            out.writeInt(N);
+            if (N > 0) {
+                for (Map.Entry<String, TimeStats> ent : mLaunchTimes.entrySet()) {
+                    out.writeString(ent.getKey());
+                    TimeStats times = ent.getValue();
+                    if (allTimes) {
+                        final int M = times.size;
+                        out.writeInt(M);
+                        out.writeInt(times.count);
+                        for (int j=0; j<M; j++) {
+                            out.writeInt(times.array[j]);
+                        }
+                    } else {
+                        times.computeStats();
+                        out.writeInt(-1);
+                        out.writeInt(times.count);
+                        out.writeInt(times.samples);
+                        out.writeInt(times.minimum);
+                        out.writeInt(times.maximum);
+                        out.writeInt(times.average);
+                        out.writeInt(times.median);
+                    }
+                }
+            }
+        }
+        
         void clear() {
+            mLaunchTimes.clear();
             mLaunchCount = 0;
             mUsageTime = 0;
         }
     }
     
-    UsageStatsService(String fileName) {
+    UsageStatsService(String dir) {
         mStats = new HashMap<String, PkgUsageStatsExtended>();
         mStatsLock = new Object();
         mFileLock = new Object();
-        mFilePrefix = fileName;
-        mCal = Calendar.getInstance();
+        mDir = new File(dir);
+        mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
+        
+        mDir.mkdir();
+        
+        // Remove any old usage files from previous versions.
+        File parentDir = mDir.getParentFile();
+        String fList[] = parentDir.list();
+        if (fList != null) {
+            String prefix = mDir.getName() + ".";
+            int i = fList.length;
+            while (i > 0) {
+                i--;
+                if (fList[i].startsWith(prefix)) {
+                    Log.i(TAG, "Deleting old usage file: " + fList[i]);
+                    (new File(parentDir, fList[i])).delete();
+                }
+            }
+        }
+        
         // Update current stats which are binned by date
-        String uFileName = getCurrentDateStr(mFilePrefix);
-        mFile = new File(uFileName);
+        mFileLeaf = getCurrentDateStr(FILE_PREFIX);
+        mFile = new File(mDir, mFileLeaf);
         readStatsFromFile();
-        mLastWriteRealTime = SystemClock.elapsedRealtime();
-        mLastTime = new Date().getTime();
+        mLastWriteElapsedTime = SystemClock.elapsedRealtime();
+        // mCal was set by getCurrentDateStr(), want to use that same time.
+        mLastWriteDay = mCal.get(Calendar.DAY_OF_YEAR);
     }
 
     /*
      * Utility method to convert date into string.
      */
     private String getCurrentDateStr(String prefix) {
-        mCal.setTime(new Date());
+        mCal.setTimeInMillis(System.currentTimeMillis());
         StringBuilder sb = new StringBuilder();
         if (prefix != null) {
             sb.append(prefix);
-            sb.append(".");
         }
+        sb.append(mCal.get(Calendar.YEAR));
         int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1;
         if (mm < 10) {
             sb.append("0");
@@ -133,7 +305,6 @@
             sb.append("0");
         }
         sb.append(dd);
-        sb.append(mCal.get(Calendar.YEAR));
         return sb.toString();
     }
     
@@ -166,11 +337,20 @@
     
     private void readStatsFLOCK(File file) throws IOException {
         Parcel in = getParcelForFile(file);
-        while (in.dataAvail() > 0) {
+        int vers = in.readInt();
+        if (vers != VERSION) {
+            Log.w(TAG, "Usage stats version changed; dropping");
+            return;
+        }
+        int N = in.readInt();
+        while (N > 0) {
+            N--;
             String pkgName = in.readString();
-            PkgUsageStatsExtended pus = new PkgUsageStatsExtended();
-            pus.mLaunchCount = in.readInt();
-            pus.mUsageTime = in.readLong();
+            if (pkgName == null) {
+                break;
+            }
+            if (localLOGV) Log.v(TAG, "Reading package #" + N + ": " + pkgName);
+            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
             synchronized (mStatsLock) {
                 mStats.put(pkgName, pus);
             }
@@ -178,27 +358,18 @@
     }
 
     private ArrayList<String> getUsageStatsFileListFLOCK() {
-        File dir = getUsageFilesDir();
-        if (dir == null) {
-            Log.w(TAG, "Couldnt find writable directory for usage stats file");
-            return null;
-        }
         // Check if there are too many files in the system and delete older files
-        String fList[] = dir.list();
+        String fList[] = mDir.list();
         if (fList == null) {
             return null;
         }
-        File pre = new File(mFilePrefix);
-        String filePrefix = pre.getName();
-        // file name followed by dot
-        int prefixLen = filePrefix.length()+1;
         ArrayList<String> fileList = new ArrayList<String>();
         for (String file : fList) {
-            int index = file.indexOf(filePrefix);
-            if (index == -1) {
+            if (!file.startsWith(FILE_PREFIX)) {
                 continue;
             }
             if (file.endsWith(".bak")) {
+                (new File(mDir, file)).delete();
                 continue;
             }
             fileList.add(file);
@@ -206,20 +377,7 @@
         return fileList;
     }
     
-    private File getUsageFilesDir() {
-        if (mFilePrefix == null) {
-            return null;
-        }
-        File pre = new File(mFilePrefix);
-        return new File(pre.getParent());
-    }
-    
     private void checkFileLimitFLOCK() {
-        File dir = getUsageFilesDir();
-        if (dir == null) {
-            Log.w(TAG, "Couldnt find writable directory for usage stats file");
-            return;
-        }
         // Get all usage stats output files
         ArrayList<String> fileList = getUsageStatsFileListFLOCK();
         if (fileList == null) {
@@ -227,49 +385,54 @@
             return;
         }
         int count = fileList.size();
-        if (count <= _MAX_NUM_FILES) {
+        if (count <= MAX_NUM_FILES) {
             return;
         }
         // Sort files
         Collections.sort(fileList);
-        count -= _MAX_NUM_FILES;
+        count -= MAX_NUM_FILES;
         // Delete older files
         for (int i = 0; i < count; i++) {
             String fileName = fileList.get(i);
-            File file = new File(dir, fileName);
-            Log.i(TAG, "Deleting file : "+fileName);
+            File file = new File(mDir, fileName);
+            Log.i(TAG, "Deleting usage file : " + fileName);
             file.delete();
         }
     }
     
-    private void writeStatsToFile() {
+    private void writeStatsToFile(boolean force) {
         synchronized (mFileLock) {
-            long currTime = new Date().getTime();
-            boolean dayChanged =  ((currTime - mLastTime) >= (24*60*60*1000));
-            long currRealTime = SystemClock.elapsedRealtime();
-            if (((currRealTime-mLastWriteRealTime) < _FILE_WRITE_INTERVAL) &&
-                    (!dayChanged)) {
-                // wait till the next update
-                return;
+            mCal.setTimeInMillis(System.currentTimeMillis());
+            final int curDay = mCal.get(Calendar.DAY_OF_YEAR);
+            // Determine if the day changed...  note that this will be wrong
+            // if the year has changed but we are in the same day of year...
+            // we can probably live with this.
+            final boolean dayChanged =  curDay != mLastWriteDay;
+            long currElapsedTime = SystemClock.elapsedRealtime();
+            if (!force) {
+                if (((currElapsedTime-mLastWriteElapsedTime) < FILE_WRITE_INTERVAL) &&
+                        (!dayChanged)) {
+                    // wait till the next update
+                    return;
+                }
             }
             // Get the most recent file
-            String todayStr = getCurrentDateStr(mFilePrefix);
+            mFileLeaf = getCurrentDateStr(FILE_PREFIX);
             // Copy current file to back up
             File backupFile =  new File(mFile.getPath() + ".bak");
             mFile.renameTo(backupFile);
             try {
-                checkFileLimitFLOCK();
-                mFile.createNewFile();
                 // Write mStats to file
-                writeStatsFLOCK();
-                mLastWriteRealTime = currRealTime;
-                mLastTime = currTime;
+                writeStatsFLOCK(!dayChanged);
+                mLastWriteElapsedTime = currElapsedTime;
                 if (dayChanged) {
+                    mLastWriteDay = curDay;
                     // clear stats
                     synchronized (mStats) {
                         mStats.clear();
                     }
-                    mFile = new File(todayStr);
+                    mFile = new File(mDir, mFileLeaf);
+                    checkFileLimitFLOCK();
                 }
                 // Delete the backup file
                 if (backupFile != null) {
@@ -278,30 +441,35 @@
             } catch (IOException e) {
                 Log.w(TAG, "Failed writing stats to file:" + mFile);
                 if (backupFile != null) {
+                    mFile.delete();
                     backupFile.renameTo(mFile);
                 }
             }
         }
     }
 
-    private void writeStatsFLOCK() throws IOException {
+    private void writeStatsFLOCK(boolean allTimes) throws IOException {
         FileOutputStream stream = new FileOutputStream(mFile);
-        Parcel out = Parcel.obtain();
-        writeStatsToParcelFLOCK(out);
-        stream.write(out.marshall());
-        out.recycle();
-        stream.flush();
-        stream.close();
+        try {
+            Parcel out = Parcel.obtain();
+            writeStatsToParcelFLOCK(out, allTimes);
+            stream.write(out.marshall());
+            out.recycle();
+            stream.flush();
+        } finally {
+            stream.close();
+        }
     }
 
-    private void writeStatsToParcelFLOCK(Parcel out) {
+    private void writeStatsToParcelFLOCK(Parcel out, boolean allTimes) {
         synchronized (mStatsLock) {
+            out.writeInt(VERSION);
             Set<String> keys = mStats.keySet();
+            out.writeInt(keys.size());
             for (String key : keys) {
                 PkgUsageStatsExtended pus = mStats.get(key);
                 out.writeString(key);
-                out.writeInt(pus.mLaunchCount);
-                out.writeLong(pus.mUsageTime);
+                pus.writeToParcel(out, allTimes);
             }
         }
     }
@@ -355,6 +523,10 @@
             return;
         }
         if (localLOGV) Log.i(TAG, "paused component:"+pkgName);
+        
+        // Persist current data to file if needed.
+        writeStatsToFile(false);
+        
         synchronized (mStatsLock) {
             PkgUsageStatsExtended pus = mStats.get(pkgName);
             if (pus == null) {
@@ -364,8 +536,25 @@
             }
             pus.updatePause();
         }
-        // Persist data to file
-        writeStatsToFile();
+    }
+    
+    public void noteLaunchTime(ComponentName componentName, int millis) {
+        enforceCallingPermission();
+        String pkgName;
+        if ((componentName == null) ||
+                ((pkgName = componentName.getPackageName()) == null)) {
+            return;
+        }
+        
+        // Persist current data to file if needed.
+        writeStatsToFile(false);
+        
+        synchronized (mStatsLock) {
+            PkgUsageStatsExtended pus = mStats.get(pkgName);
+            if (pus != null) {
+                pus.addLaunchTime(componentName.getClassName(), millis);
+            }
+        }
     }
     
     public void enforceCallingPermission() {
@@ -432,27 +621,25 @@
         }
     }
     
-    private void collectDumpInfoFLOCK(PrintWriter pw, String[] args) {
+    private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput,
+            boolean deleteAfterPrint) {
         List<String> fileList = getUsageStatsFileListFLOCK();
         if (fileList == null) {
             return;
         }
-        final boolean isCheckinRequest = scanArgs(args, "-c");
         Collections.sort(fileList);
-        File usageFile = new File(mFilePrefix);
-        String dirName = usageFile.getParent();
-        File dir = new File(dirName);
-        String filePrefix = usageFile.getName();
-        // file name followed by dot
-        int prefixLen = filePrefix.length()+1;
-        String todayStr = getCurrentDateStr(null);
         for (String file : fileList) {
-            File dFile = new File(dir, file);
-            String dateStr = file.substring(prefixLen);
+            if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) {
+                // In this mode we don't print the current day's stats, since
+                // they are incomplete.
+                continue;
+            }
+            File dFile = new File(mDir, file);
+            String dateStr = file.substring(FILE_PREFIX.length());
             try {
                 Parcel in = getParcelForFile(dFile);
-                collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCheckinRequest);
-                if (isCheckinRequest && !todayStr.equalsIgnoreCase(dateStr)) {
+                collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput);
+                if (deleteAfterPrint) {
                     // Delete old file after collecting info only for checkin requests
                     dFile.delete();
                 }
@@ -466,40 +653,102 @@
     }
     
     private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw,
-            String date, boolean isCheckinRequest) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("Date:");
-        sb.append(date);
-        boolean first = true;
-        while (in.dataAvail() > 0) {
-            String pkgName = in.readString();
-            int launchCount = in.readInt();
-            long usageTime = in.readLong();
-            if (isCheckinRequest) {
-                if (!first) {
-                    sb.append(",");
-                }
-                sb.append(pkgName);
-                sb.append(",");
-                sb.append(launchCount);
-                sb.append(",");
-                sb.append(usageTime);
-                sb.append("ms");
-            } else {
-                if (first) {
-                    sb.append("\n");
-                }
-                sb.append("pkg=");
-                sb.append(pkgName);
-                sb.append(", launchCount=");
-                sb.append(launchCount);
-                sb.append(", usageTime=");
-                sb.append(usageTime);
-                sb.append(" ms\n");
-            }
-            first = false;
+            String date, boolean isCompactOutput) {
+        StringBuilder sb = new StringBuilder(512);
+        if (isCompactOutput) {
+            sb.append("D:");
+            sb.append(CHECKIN_VERSION);
+            sb.append(',');
+        } else {
+            sb.append("Date: ");
         }
-        pw.write(sb.toString());
+        
+        sb.append(date);
+        
+        int vers = in.readInt();
+        if (vers != VERSION) {
+            sb.append(" (old data version)");
+            pw.println(sb.toString());
+            return;
+        }
+        
+        pw.println(sb.toString());
+        int N = in.readInt();
+        
+        while (N > 0) {
+            N--;
+            String pkgName = in.readString();
+            if (pkgName == null) {
+                break;
+            }
+            sb.setLength(0);
+            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
+            if (isCompactOutput) {
+                sb.append("P:");
+                sb.append(pkgName);
+                sb.append(",");
+                sb.append(pus.mLaunchCount);
+                sb.append(",");
+                sb.append(pus.mUsageTime);
+                sb.append('\n');
+                final int NC = pus.mLaunchTimes.size();
+                if (NC > 0) {
+                    for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) {
+                        sb.append("A:");
+                        sb.append(ent.getKey());
+                        sb.append(",");
+                        TimeStats times = ent.getValue();
+                        times.computeStats();
+                        sb.append(times.count);
+                        sb.append(",");
+                        sb.append(times.samples);
+                        sb.append(",");
+                        sb.append(times.minimum);
+                        sb.append(",");
+                        sb.append(times.maximum);
+                        sb.append(",");
+                        sb.append(times.average);
+                        sb.append(",");
+                        sb.append(times.median);
+                        sb.append('\n');
+                    }
+                }
+                
+            } else {
+                sb.append("  ");
+                sb.append(pkgName);
+                sb.append(": ");
+                sb.append(pus.mLaunchCount);
+                sb.append(" times, ");
+                sb.append(pus.mUsageTime);
+                sb.append(" ms");
+                sb.append('\n');
+                final int NC = pus.mLaunchTimes.size();
+                if (NC > 0) {
+                    for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) {
+                        sb.append("    ");
+                        sb.append(ent.getKey());
+                        TimeStats times = ent.getValue();
+                        times.computeStats();
+                        sb.append(": count=");
+                        sb.append(times.count);
+                        sb.append(", samples=");
+                        sb.append(times.samples);
+                        sb.append(", min=");
+                        sb.append(times.minimum);
+                        sb.append(", max=");
+                        sb.append(times.maximum);
+                        sb.append(", avg=");
+                        sb.append(times.average);
+                        sb.append(", med=");
+                        sb.append(times.median);
+                        sb.append('\n');
+                    }
+                }
+            }
+            
+            pw.write(sb.toString());
+        }
     }
     
     /**
@@ -524,8 +773,19 @@
      * The data persisted to file is parsed and the stats are computed. 
      */
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final boolean isCheckinRequest = scanArgs(args, "--checkin");
+        final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c");
+        final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d");
+        
+        // Make sure the current stats are written to the file.  This
+        // doesn't need to be done if we are deleting files after printing,
+        // since it that case we won't print the current stats.
+        if (!deleteAfterPrint) {
+            writeStatsToFile(true);
+        }
+        
         synchronized (mFileLock) {
-            collectDumpInfoFLOCK(pw, args);
+            collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint);
         }
     }