am 9ee17b7d: am 866362fc: Merge "Usage Stats: don\'t block writing stats to disk." into gingerbread

Merge commit '9ee17b7dec7c76830b5add29f3b97039e178629a'

* commit '9ee17b7dec7c76830b5add29f3b97039e178629a':
  Usage Stats: don't block writing stats to disk.
diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java
index 1b9e1c7..3f15d0a 100644
--- a/services/java/com/android/server/am/UsageStatsService.java
+++ b/services/java/com/android/server/am/UsageStatsService.java
@@ -44,6 +44,9 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * This service collects the statistics associated with usage
@@ -88,11 +91,13 @@
     private boolean mIsResumed;
     private File mFile;
     private String mFileLeaf;
-    //private File mBackupFile;
-    private long mLastWriteElapsedTime;
     private File mDir;
-    private Calendar mCal;
-    private int mLastWriteDay;
+
+    private Calendar mCal; // guarded by itself
+
+    private final AtomicInteger mLastWriteDay = new AtomicInteger(-1);
+    private final AtomicLong mLastWriteElapsedTime = new AtomicLong(0);
+    private final AtomicBoolean mUnforcedDiskWriteRunning = new AtomicBoolean(false);
     
     static class TimeStats {
         int count;
@@ -241,31 +246,33 @@
         mFileLeaf = getCurrentDateStr(FILE_PREFIX);
         mFile = new File(mDir, mFileLeaf);
         readStatsFromFile();
-        mLastWriteElapsedTime = SystemClock.elapsedRealtime();
+        mLastWriteElapsedTime.set(SystemClock.elapsedRealtime());
         // mCal was set by getCurrentDateStr(), want to use that same time.
-        mLastWriteDay = mCal.get(Calendar.DAY_OF_YEAR);
+        mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR));
     }
 
     /*
      * Utility method to convert date into string.
      */
     private String getCurrentDateStr(String prefix) {
-        mCal.setTimeInMillis(System.currentTimeMillis());
         StringBuilder sb = new StringBuilder();
-        if (prefix != null) {
-            sb.append(prefix);
+        synchronized (mCal) {
+            mCal.setTimeInMillis(System.currentTimeMillis());
+            if (prefix != null) {
+                sb.append(prefix);
+            }
+            sb.append(mCal.get(Calendar.YEAR));
+            int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1;
+            if (mm < 10) {
+                sb.append("0");
+            }
+            sb.append(mm);
+            int dd = mCal.get(Calendar.DAY_OF_MONTH);
+            if (dd < 10) {
+                sb.append("0");
+            }
+            sb.append(dd);
         }
-        sb.append(mCal.get(Calendar.YEAR));
-        int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1;
-        if (mm < 10) {
-            sb.append("0");
-        }
-        sb.append(mm);
-        int dd = mCal.get(Calendar.DAY_OF_MONTH);
-        if (dd < 10) {
-            sb.append("0");
-        }
-        sb.append(dd);
         return sb.toString();
     }
     
@@ -360,23 +367,56 @@
             file.delete();
         }
     }
-    
-    private void writeStatsToFile(boolean force) {
-        synchronized (mFileLock) {
+
+    /**
+     * Conditionally start up a disk write if it's been awhile, or the
+     * day has rolled over.
+     *
+     * This is called indirectly from user-facing actions (when
+     * 'force' is false) so it tries to be quick, without writing to
+     * disk directly or acquiring heavy locks.
+     *
+     * @params force  do an unconditional, synchronous stats flush
+     *                to disk on the current thread.
+     */
+    private void writeStatsToFile(final boolean force) {
+        int curDay;
+        synchronized (mCal) {
             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;
-                }
+            curDay = mCal.get(Calendar.DAY_OF_YEAR);
+        }
+        final boolean dayChanged = curDay != mLastWriteDay.get();
+
+        // 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 long currElapsedTime = SystemClock.elapsedRealtime();
+
+        // Fast common path, without taking the often-contentious
+        // mFileLock.
+        if (!force) {
+            if (!dayChanged &&
+                (currElapsedTime - mLastWriteElapsedTime.get()) < FILE_WRITE_INTERVAL) {
+                // wait till the next update
+                return;
             }
+            if (mUnforcedDiskWriteRunning.compareAndSet(false, true)) {
+                new Thread("UsageStatsService_DiskWriter") {
+                    public void run() {
+                        try {
+                            Slog.d(TAG, "Disk writer thread starting.");
+                            writeStatsToFile(true);
+                        } finally {
+                            mUnforcedDiskWriteRunning.set(false);
+                            Slog.d(TAG, "Disk writer thread ending.");
+                        }
+                    }
+                }.start();
+            }
+            return;
+        }
+
+        synchronized (mFileLock) {
             // Get the most recent file
             mFileLeaf = getCurrentDateStr(FILE_PREFIX);
             // Copy current file to back up
@@ -395,10 +435,10 @@
 
             try {
                 // Write mStats to file
-                writeStatsFLOCK();
-                mLastWriteElapsedTime = currElapsedTime;
+                writeStatsFLOCK(mFile);
+                mLastWriteElapsedTime.set(currElapsedTime);
                 if (dayChanged) {
-                    mLastWriteDay = curDay;
+                    mLastWriteDay.set(curDay);
                     // clear stats
                     synchronized (mStats) {
                         mStats.clear();
@@ -418,10 +458,11 @@
                 }
             }
         }
+        Slog.d(TAG, "Dumped usage stats.");
     }
 
-    private void writeStatsFLOCK() throws IOException {
-        FileOutputStream stream = new FileOutputStream(mFile);
+    private void writeStatsFLOCK(File file) throws IOException {
+        FileOutputStream stream = new FileOutputStream(file);
         try {
             Parcel out = Parcel.obtain();
             writeStatsToParcelFLOCK(out);