Merge "Add dumping the state of the print sub-system." into klp-dev
diff --git a/api/current.txt b/api/current.txt
index 5ec1771..78abd53 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29053,12 +29053,12 @@
   }
 
   public class CaptioningManager {
-    method public void addCaptioningStateChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
+    method public void addCaptioningChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
     method public final float getFontScale();
     method public final java.util.Locale getLocale();
     method public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle();
     method public final boolean isEnabled();
-    method public void removeCaptioningStateChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
+    method public void removeCaptioningChangeListener(android.view.accessibility.CaptioningManager.CaptioningChangeListener);
   }
 
   public static final class CaptioningManager.CaptionStyle {
@@ -29072,7 +29072,7 @@
     field public final int foregroundColor;
   }
 
-  public abstract class CaptioningManager.CaptioningChangeListener {
+  public static abstract class CaptioningManager.CaptioningChangeListener {
     ctor public CaptioningManager.CaptioningChangeListener();
     method public void onEnabledChanged(boolean);
     method public void onFontScaleChanged(float);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 7c40bb1..2d28280 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2106,7 +2106,12 @@
         }
         // If the target is not exported, then nobody else can get to it.
         if (!exported) {
-            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid);
+            /*
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
+                    here);
+            */
             return PackageManager.PERMISSION_DENIED;
         }
         if (permission == null) {
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index d4c6abe..557239f 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -140,7 +140,7 @@
      *
      * @param listener the listener to add
      */
-    public void addCaptioningStateChangeListener(CaptioningChangeListener listener) {
+    public void addCaptioningChangeListener(CaptioningChangeListener listener) {
         synchronized (mListeners) {
             if (mListeners.isEmpty()) {
                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED);
@@ -163,11 +163,11 @@
 
     /**
      * Removes a listener previously added using
-     * {@link #addCaptioningStateChangeListener}.
+     * {@link #addCaptioningChangeListener}.
      *
      * @param listener the listener to remove
      */
-    public void removeCaptioningStateChangeListener(CaptioningChangeListener listener) {
+    public void removeCaptioningChangeListener(CaptioningChangeListener listener) {
         synchronized (mListeners) {
             mListeners.remove(listener);
 
@@ -366,7 +366,7 @@
      * Listener for changes in captioning properties, including enabled state
      * and user style preferences.
      */
-    public abstract class CaptioningChangeListener {
+    public static abstract class CaptioningChangeListener {
         /**
          * Called when the captioning enabled state changes.
          *
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index f449797..009b729 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -30,6 +30,7 @@
 import android.media.MediaPlayer.OnInfoListener;
 import android.media.Metadata;
 import android.media.SubtitleController;
+import android.media.SubtitleTrack.RenderingWidget;
 import android.media.WebVttRenderer;
 import android.net.Uri;
 import android.util.AttributeSet;
@@ -46,7 +47,6 @@
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.ArrayList;
 import java.util.Map;
 import java.util.Vector;
 
@@ -100,14 +100,11 @@
     private boolean     mCanSeekBack;
     private boolean     mCanSeekForward;
 
-    /** List of views overlaid on top of the video. */
-    private ArrayList<View> mOverlays;
+    /** Subtitle rendering widget overlaid on top of the video. */
+    private RenderingWidget mSubtitleWidget;
 
-    /**
-     * Listener for overlay layout changes. Invalidates the video view to ensure
-     * that captions are redrawn whenever their layout changes.
-     */
-    private OnLayoutChangeListener mOverlayLayoutListener;
+    /** Listener for changes to subtitle data, used to redraw when needed. */
+    private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
 
     public VideoView(Context context) {
         super(context);
@@ -302,11 +299,10 @@
             mMediaPlayer = new MediaPlayer();
             // TODO: create SubtitleController in MediaPlayer, but we need
             // a context for the subtitle renderers
-            SubtitleController controller = new SubtitleController(
-                    getContext(),
-                    mMediaPlayer.getMediaTimeProvider(),
-                    mMediaPlayer);
-            controller.registerRenderer(new WebVttRenderer(getContext(), null));
+            final Context context = getContext();
+            final SubtitleController controller = new SubtitleController(
+                    context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
+            controller.registerRenderer(new WebVttRenderer(context));
             mMediaPlayer.setSubtitleAnchor(controller, this);
 
             if (mAudioSession != 0) {
@@ -792,12 +788,29 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (mSubtitleWidget != null) {
+            mSubtitleWidget.onAttachedToWindow();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (mSubtitleWidget != null) {
+            mSubtitleWidget.onDetachedFromWindow();
+        }
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
-        // Layout overlay views, if necessary.
-        if (changed && mOverlays != null && !mOverlays.isEmpty()) {
-            measureAndLayoutOverlays();
+        if (mSubtitleWidget != null) {
+            measureAndLayoutSubtitleWidget();
         }
     }
 
@@ -805,104 +818,65 @@
     public void draw(Canvas canvas) {
         super.draw(canvas);
 
-        final int count = mOverlays.size();
-        for (int i = 0; i < count; i++) {
-            final View overlay = mOverlays.get(i);
-            overlay.draw(canvas);
+        if (mSubtitleWidget != null) {
+            final int saveCount = canvas.save();
+            canvas.translate(getPaddingLeft(), getPaddingTop());
+            mSubtitleWidget.draw(canvas);
+            canvas.restoreToCount(saveCount);
         }
     }
 
     /**
-     * Adds a view to be overlaid on top of this video view. During layout, the
-     * view will be forced to match the bounds, less padding, of the video view.
-     * <p>
-     * Overlays are drawn in the order they are added. The last added overlay
-     * will be drawn on top.
-     *
-     * @param overlay the view to overlay
-     * @see #removeOverlay(View)
-     */
-    private void addOverlay(View overlay) {
-        if (mOverlays == null) {
-            mOverlays = new ArrayList<View>(1);
-        }
-
-        if (mOverlayLayoutListener == null) {
-            mOverlayLayoutListener = new OnLayoutChangeListener() {
-                @Override
-                public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                    invalidate();
-                }
-            };
-        }
-
-        if (mOverlays.isEmpty()) {
-            setWillNotDraw(false);
-        }
-
-        mOverlays.add(overlay);
-        overlay.addOnLayoutChangeListener(mOverlayLayoutListener);
-        measureAndLayoutOverlays();
-    }
-
-    /**
-     * Removes a view previously added using {@link #addOverlay}.
-     *
-     * @param overlay the view to remove
-     * @see #addOverlay(View)
-     */
-    private void removeOverlay(View overlay) {
-        if (mOverlays == null) {
-            return;
-        }
-
-        overlay.removeOnLayoutChangeListener(mOverlayLayoutListener);
-        mOverlays.remove(overlay);
-
-        if (mOverlays.isEmpty()) {
-            setWillNotDraw(true);
-        }
-
-        invalidate();
-    }
-
-    /**
      * Forces a measurement and layout pass for all overlaid views.
      *
-     * @see #addOverlay(View)
+     * @see #setSubtitleWidget(RenderingWidget)
      */
-    private void measureAndLayoutOverlays() {
-        final int left = getPaddingLeft();
-        final int top = getPaddingTop();
-        final int right = getWidth() - left - getPaddingRight();
-        final int bottom = getHeight() - top - getPaddingBottom();
-        final int widthSpec = MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY);
-        final int heightSpec = MeasureSpec.makeMeasureSpec(bottom - top, MeasureSpec.EXACTLY);
+    private void measureAndLayoutSubtitleWidget() {
+        final int width = getWidth() - getPaddingLeft() - getPaddingRight();
+        final int height = getHeight() - getPaddingTop() - getPaddingBottom();
 
-        final int count = mOverlays.size();
-        for (int i = 0; i < count; i++) {
-            final View overlay = mOverlays.get(i);
-            overlay.measure(widthSpec, heightSpec);
-            overlay.layout(left, top, right, bottom);
-        }
+        mSubtitleWidget.setSize(width, height);
     }
 
     /** @hide */
     @Override
-    public void setSubtitleView(View view) {
-        if (mSubtitleView == view) {
+    public void setSubtitleWidget(RenderingWidget subtitleWidget) {
+        if (mSubtitleWidget == subtitleWidget) {
             return;
         }
 
-        if (mSubtitleView != null) {
-            removeOverlay(mSubtitleView);
-        }
-        mSubtitleView = view;
-        if (mSubtitleView != null) {
-            addOverlay(mSubtitleView);
-        }
-    }
+        final boolean attachedToWindow = isAttachedToWindow();
+        if (mSubtitleWidget != null) {
+            if (attachedToWindow) {
+                mSubtitleWidget.onDetachedFromWindow();
+            }
 
-    private View mSubtitleView;
+            mSubtitleWidget.setOnChangedListener(null);
+        }
+
+        mSubtitleWidget = subtitleWidget;
+
+        if (subtitleWidget != null) {
+            if (mSubtitlesChangedListener == null) {
+                mSubtitlesChangedListener = new RenderingWidget.OnChangedListener() {
+                    @Override
+                    public void onChanged(RenderingWidget renderingWidget) {
+                        invalidate();
+                    }
+                };
+            }
+
+            setWillNotDraw(false);
+            subtitleWidget.setOnChangedListener(mSubtitlesChangedListener);
+
+            if (attachedToWindow) {
+                subtitleWidget.onAttachedToWindow();
+                requestLayout();
+            }
+        } else {
+            setWillNotDraw(true);
+        }
+
+        invalidate();
+    }
 }
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 16b119a..1f55a4c 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -165,7 +165,7 @@
     static final String CSV_SEP = "\t";
 
     // Current version of the parcel format.
-    private static final int PARCEL_VERSION = 9;
+    private static final int PARCEL_VERSION = 11;
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
     private static final int MAGIC = 0x50535453;
 
@@ -204,6 +204,12 @@
     int[] mAddLongTable;
     int mAddLongTableSize;
 
+    // For writing parcels.
+    ArrayMap<String, Integer> mCommonStringToIndex;
+
+    // For reading parcels.
+    ArrayList<String> mIndexToCommonString;
+
     public ProcessStats(boolean running) {
         mRunning = running;
         reset();
@@ -247,7 +253,7 @@
                     if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
                             + " service " + otherSvc.mName);
                     ServiceState thisSvc = getServiceStateLocked(pkgName, uid,
-                            null, otherSvc.mName);
+                            otherSvc.mProcessName, otherSvc.mName);
                     thisSvc.add(otherSvc);
                 }
             }
@@ -959,7 +965,15 @@
         for (int ip=procMap.size()-1; ip>=0; ip--) {
             SparseArray<ProcessState> uids = procMap.valueAt(ip);
             for (int iu=uids.size()-1; iu>=0; iu--) {
-                uids.valueAt(iu).resetSafely(now);
+                ProcessState ps = uids.valueAt(iu);
+                if (ps.isInUse()) {
+                    uids.valueAt(iu).resetSafely(now);
+                } else {
+                    uids.removeAt(iu);
+                }
+            }
+            if (uids.size() <= 0) {
+                procMap.removeAt(ip);
             }
         }
         ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
@@ -968,16 +982,27 @@
             for (int iu=uids.size()-1; iu>=0; iu--) {
                 PackageState pkgState = uids.valueAt(iu);
                 for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) {
-                    pkgState.mProcesses.valueAt(iproc).resetSafely(now);
+                    ProcessState ps = pkgState.mProcesses.valueAt(iproc);
+                    if (ps.isInUse()) {
+                        pkgState.mProcesses.valueAt(iproc).resetSafely(now);
+                    } else {
+                        pkgState.mProcesses.removeAt(iproc);
+                    }
                 }
                 for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) {
                     ServiceState ss = pkgState.mServices.valueAt(isvc);
-                    if (ss.isActive()) {
+                    if (ss.isInUse()) {
                         pkgState.mServices.valueAt(isvc).resetSafely(now);
                     } else {
                         pkgState.mServices.removeAt(isvc);
                     }
                 }
+                if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) {
+                    uids.removeAt(iu);
+                }
+            }
+            if (uids.size() <= 0) {
+                pkgMap.removeAt(ip);
             }
         }
         mStartTime = SystemClock.uptimeMillis();
@@ -1048,6 +1073,75 @@
         return table;
     }
 
+    private void writeCompactedLongArray(Parcel out, long[] array) {
+        final int N = array.length;
+        out.writeInt(N);
+        for (int i=0; i<N; i++) {
+            long val = array[i];
+            if (val < 0) {
+                Slog.w(TAG, "Time val negative: " + val);
+                val = 0;
+            }
+            if (val <= Integer.MAX_VALUE) {
+                out.writeInt((int)val);
+            } else {
+                int top = ~((int)((val>>32)&0x7fffffff));
+                int bottom = (int)(val&0xfffffff);
+                out.writeInt(top);
+                out.writeInt(bottom);
+            }
+        }
+    }
+
+    private void readCompactedLongArray(Parcel in, int version, long[] array) {
+        if (version <= 10) {
+            in.readLongArray(array);
+            return;
+        }
+        final int N = in.readInt();
+        if (N != array.length) {
+            throw new RuntimeException("bad array lengths");
+        }
+        for (int i=0; i<N; i++) {
+            int val = in.readInt();
+            if (val >= 0) {
+                array[i] = val;
+            } else {
+                int bottom = in.readInt();
+                array[i] = (((long)~val)<<32) | bottom;
+            }
+        }
+    }
+
+    private void writeCommonString(Parcel out, String name) {
+        Integer index = mCommonStringToIndex.get(name);
+        if (index != null) {
+            out.writeInt(index);
+            return;
+        }
+        index = mCommonStringToIndex.size();
+        mCommonStringToIndex.put(name, index);
+        out.writeInt(~index);
+        out.writeString(name);
+    }
+
+    private String readCommonString(Parcel in, int version) {
+        if (version <= 9) {
+            return in.readString();
+        }
+        int index = in.readInt();
+        if (index >= 0) {
+            return mIndexToCommonString.get(index);
+        }
+        index = ~index;
+        String name = in.readString();
+        while (mIndexToCommonString.size() <= index) {
+            mIndexToCommonString.add(null);
+        }
+        mIndexToCommonString.set(index, name);
+        return name;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -1063,6 +1157,8 @@
         out.writeInt(PSS_COUNT);
         out.writeInt(LONGS_SIZE);
 
+        mCommonStringToIndex = new ArrayMap<String, Integer>(mProcesses.mMap.size());
+
         // First commit all running times.
         ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
         final int NPROC = procMap.size();
@@ -1104,7 +1200,7 @@
         out.writeInt(mLongs.size());
         out.writeInt(mNextLong);
         for (int i=0; i<(mLongs.size()-1); i++) {
-            out.writeLongArray(mLongs.get(i));
+            writeCompactedLongArray(out, mLongs.get(i));
         }
         long[] lastLongs = mLongs.get(mLongs.size() - 1);
         for (int i=0; i<mNextLong; i++) {
@@ -1116,24 +1212,24 @@
             mMemFactorDurations[mMemFactor] += now - mStartTime;
             mStartTime = now;
         }
-        out.writeLongArray(mMemFactorDurations);
+        writeCompactedLongArray(out, mMemFactorDurations);
 
         out.writeInt(NPROC);
         for (int ip=0; ip<NPROC; ip++) {
-            out.writeString(procMap.keyAt(ip));
+            writeCommonString(out, procMap.keyAt(ip));
             SparseArray<ProcessState> uids = procMap.valueAt(ip);
             final int NUID = uids.size();
             out.writeInt(NUID);
             for (int iu=0; iu<NUID; iu++) {
                 out.writeInt(uids.keyAt(iu));
                 ProcessState proc = uids.valueAt(iu);
-                out.writeString(proc.mPackage);
+                writeCommonString(out, proc.mPackage);
                 proc.writeToParcel(out, now);
             }
         }
         out.writeInt(NPKG);
         for (int ip=0; ip<NPKG; ip++) {
-            out.writeString(pkgMap.keyAt(ip));
+            writeCommonString(out, pkgMap.keyAt(ip));
             SparseArray<PackageState> uids = pkgMap.valueAt(ip);
             final int NUID = uids.size();
             out.writeInt(NUID);
@@ -1143,7 +1239,7 @@
                 final int NPROCS = pkgState.mProcesses.size();
                 out.writeInt(NPROCS);
                 for (int iproc=0; iproc<NPROCS; iproc++) {
-                    out.writeString(pkgState.mProcesses.keyAt(iproc));
+                    writeCommonString(out, pkgState.mProcesses.keyAt(iproc));
                     ProcessState proc = pkgState.mProcesses.valueAt(iproc);
                     if (proc.mCommonProcess == proc) {
                         // This is the same as the common process we wrote above.
@@ -1159,10 +1255,13 @@
                 for (int isvc=0; isvc<NSRVS; isvc++) {
                     out.writeString(pkgState.mServices.keyAt(isvc));
                     ServiceState svc = pkgState.mServices.valueAt(isvc);
+                    writeCommonString(out, svc.mProcessName);
                     svc.writeToParcel(out, now);
                 }
             }
         }
+
+        mCommonStringToIndex = null;
     }
 
     private boolean readCheckedInt(Parcel in, int val, String what) {
@@ -1222,7 +1321,7 @@
             return;
         }
         int version = in.readInt();
-        if (version != PARCEL_VERSION && version != 6) {
+        if (version != PARCEL_VERSION) {
             mReadError = "bad version: " + version;
             return;
         }
@@ -1239,14 +1338,14 @@
             return;
         }
 
+        mIndexToCommonString = new ArrayList<String>();
+
         mTimePeriodStartClock = in.readLong();
         buildTimePeriodStartClockStr();
         mTimePeriodStartRealtime = in.readLong();
         mTimePeriodEndRealtime = in.readLong();
-        if (version ==  PARCEL_VERSION) {
-            mRuntime = in.readString();
-            mWebView = in.readString();
-        }
+        mRuntime = in.readString();
+        mWebView = in.readString();
         mFlags = in.readInt();
 
         final int NLONGS = in.readInt();
@@ -1256,7 +1355,7 @@
             while (i >= mLongs.size()) {
                 mLongs.add(new long[LONGS_SIZE]);
             }
-            in.readLongArray(mLongs.get(i));
+            readCompactedLongArray(in, version, mLongs.get(i));
         }
         long[] longs = new long[LONGS_SIZE];
         mNextLong = NEXTLONG;
@@ -1266,7 +1365,7 @@
         }
         mLongs.add(longs);
 
-        in.readLongArray(mMemFactorDurations);
+        readCompactedLongArray(in, version, mMemFactorDurations);
 
         int NPROC = in.readInt();
         if (NPROC < 0) {
@@ -1275,7 +1374,7 @@
         }
         while (NPROC > 0) {
             NPROC--;
-            String procName = in.readString();
+            String procName = readCommonString(in, version);
             if (procName == null) {
                 mReadError = "bad process name";
                 return;
@@ -1292,7 +1391,7 @@
                     mReadError = "bad uid: " + uid;
                     return;
                 }
-                String pkgName = in.readString();
+                String pkgName = readCommonString(in, version);
                 if (pkgName == null) {
                     mReadError = "bad process package name";
                     return;
@@ -1322,7 +1421,7 @@
         }
         while (NPKG > 0) {
             NPKG--;
-            String pkgName = in.readString();
+            String pkgName = readCommonString(in, version);
             if (pkgName == null) {
                 mReadError = "bad package name";
                 return;
@@ -1348,7 +1447,7 @@
                 }
                 while (NPROCS > 0) {
                     NPROCS--;
-                    String procName = in.readString();
+                    String procName = readCommonString(in, version);
                     if (procName == null) {
                         mReadError = "bad package process name";
                         return;
@@ -1400,9 +1499,10 @@
                         mReadError = "bad package service name";
                         return;
                     }
+                    String processName = version > 9 ? readCommonString(in, version) : null;
                     ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null;
                     if (serv == null) {
-                        serv = new ServiceState(this, pkgName, serviceName, null);
+                        serv = new ServiceState(this, pkgName, serviceName, processName, null);
                     }
                     if (!serv.readFromParcel(in)) {
                         return;
@@ -1414,6 +1514,8 @@
             }
         }
 
+        mIndexToCommonString = null;
+
         if (DEBUG) Slog.d(TAG, "Successfully read procstats!");
     }
 
@@ -1555,12 +1657,11 @@
         final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid);
         ProcessStats.ServiceState ss = as.mServices.get(className);
         if (ss != null) {
-            ss.makeActive();
             return ss;
         }
         final ProcessStats.ProcessState ps = processName != null
                 ? getProcessStateLocked(packageName, uid, processName) : null;
-        ss = new ProcessStats.ServiceState(this, packageName, className, ps);
+        ss = new ProcessStats.ServiceState(this, packageName, className, processName, ps);
         as.mServices.put(className, ss);
         return ss;
     }
@@ -1602,10 +1703,10 @@
                                 ALL_PROC_STATES, now);
                         dumpProcessPss(pw, "        ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
                                 ALL_PROC_STATES);
-                        if (dumpAll) {
-                            pw.print("        mNumStartedServices=");
-                                    pw.println(proc.mNumStartedServices);
-                        }
+                        pw.print("        mActive="); pw.println(proc.mActive);
+                        pw.print("        mNumActiveServices="); pw.print(proc.mNumActiveServices);
+                                pw.print(" mNumStartedServices=");
+                                pw.println(proc.mNumStartedServices);
                     }
                 } else {
                     ArrayList<ProcessState> procs = new ArrayList<ProcessState>();
@@ -1624,6 +1725,9 @@
                     pw.print(pkgState.mServices.keyAt(isvc));
                     pw.println(":");
                     ServiceState svc = pkgState.mServices.valueAt(isvc);
+                    dumpServiceStats(pw, "        ", "          ", "    ", "Running", svc,
+                            svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState,
+                            svc.mRunStartTime, now, totalTime, dumpAll);
                     dumpServiceStats(pw, "        ", "          ", "    ", "Started", svc,
                             svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState,
                             svc.mStartedStartTime, now, totalTime, dumpAll);
@@ -1633,6 +1737,9 @@
                     dumpServiceStats(pw, "        ", "          ", "  ", "Executing", svc,
                             svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState,
                             svc.mExecStartTime, now, totalTime, dumpAll);
+                    if (dumpAll) {
+                        pw.print("        mActive="); pw.println(svc.mActive);
+                    }
                 }
             }
         }
@@ -1663,6 +1770,12 @@
                             ALL_PROC_STATES, now);
                     dumpProcessPss(pw, "        ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
                             ALL_PROC_STATES);
+                    if (dumpAll) {
+                        pw.print("        mActive="); pw.println(proc.mActive);
+                        pw.print("        mNumActiveServices="); pw.print(proc.mNumActiveServices);
+                                pw.print(" mNumStartedServices=");
+                                pw.println(proc.mNumStartedServices);
+                    }
                 }
             }
 
@@ -1929,6 +2042,9 @@
                     String serviceName = collapseString(pkgName,
                             pkgState.mServices.keyAt(isvc));
                     ServiceState svc = pkgState.mServices.valueAt(isvc);
+                    dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, serviceName,
+                            svc, ServiceState.SERVICE_RUN, svc.mRunCount,
+                            svc.mRunState, svc.mRunStartTime, now);
                     dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, serviceName,
                             svc, ServiceState.SERVICE_STARTED, svc.mStartedCount,
                             svc.mStartedState, svc.mStartedStartTime, now);
@@ -2003,6 +2119,8 @@
         int[] mPssTable;
         int mPssTableSize;
 
+        boolean mActive;
+        int mNumActiveServices;
         int mNumStartedServices;
 
         int mNumExcessiveWake;
@@ -2072,6 +2190,7 @@
             }
             pnew.mNumExcessiveWake = mNumExcessiveWake;
             pnew.mNumExcessiveCpu = mNumExcessiveCpu;
+            pnew.mActive = mActive;
             pnew.mNumStartedServices = mNumStartedServices;
             return pnew;
         }
@@ -2151,6 +2270,18 @@
             return true;
         }
 
+        public void makeActive() {
+            mActive = true;
+        }
+
+        public void makeInactive() {
+            mActive = false;
+        }
+
+        public boolean isInUse() {
+            return mActive || mNumActiveServices > 0 || mNumStartedServices > 0;
+        }
+
         /**
          * Update the current state of the given list of processes.
          *
@@ -2219,6 +2350,24 @@
             longs[(off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK] += dur;
         }
 
+        void incActiveServices() {
+            if (mCommonProcess != this) {
+                mCommonProcess.incActiveServices();
+            }
+            mNumActiveServices++;
+        }
+
+        void decActiveServices() {
+            if (mCommonProcess != this) {
+                mCommonProcess.decActiveServices();
+            }
+            mNumActiveServices--;
+            if (mNumActiveServices < 0) {
+                throw new IllegalStateException("Proc active services underrun: pkg="
+                        + mPackage + " uid=" + mUid + " name=" + mName);
+            }
+        }
+
         void incStartedServices(int memFactor, long now) {
             if (mCommonProcess != this) {
                 mCommonProcess.incStartedServices(memFactor, now);
@@ -2406,18 +2555,24 @@
         final ProcessStats mStats;
         public final String mPackage;
         public final String mName;
+        public final String mProcessName;
         ProcessState mProc;
 
-        int mActive = 1;
+        int mActive = 0;
 
-        public static final int SERVICE_STARTED = 0;
-        public static final int SERVICE_BOUND = 1;
-        public static final int SERVICE_EXEC = 2;
-        static final int SERVICE_COUNT = 3;
+        public static final int SERVICE_RUN = 0;
+        public static final int SERVICE_STARTED = 1;
+        public static final int SERVICE_BOUND = 2;
+        public static final int SERVICE_EXEC = 3;
+        static final int SERVICE_COUNT = 4;
 
         int[] mDurationsTable;
         int mDurationsTableSize;
 
+        int mRunCount;
+        public int mRunState = STATE_NOTHING;
+        long mRunStartTime;
+
         int mStartedCount;
         public int mStartedState = STATE_NOTHING;
         long mStartedStartTime;
@@ -2430,14 +2585,19 @@
         public int mExecState = STATE_NOTHING;
         long mExecStartTime;
 
-        public ServiceState(ProcessStats processStats, String pkg, String name, ProcessState proc) {
+        public ServiceState(ProcessStats processStats, String pkg, String name,
+                String processName, ProcessState proc) {
             mStats = processStats;
             mPackage = pkg;
             mName = name;
+            mProcessName = processName;
             mProc = proc;
         }
 
         public void makeActive() {
+            if (mActive == 0) {
+                mProc.incActiveServices();
+            }
             mActive++;
         }
 
@@ -2448,9 +2608,12 @@
             Slog.i(TAG, "Making " + this + " inactive", here);
             */
             mActive--;
+            if (mActive == 0) {
+                mProc.decActiveServices();
+            }
         }
 
-        public boolean isActive() {
+        public boolean isInUse() {
             return mActive > 0;
         }
 
@@ -2460,6 +2623,7 @@
                 int state = (ent>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
                 addStateTime(state, other.mStats.getLong(ent, 0));
             }
+            mRunCount += other.mRunCount;
             mStartedCount += other.mStartedCount;
             mBoundCount += other.mBoundCount;
             mExecCount += other.mExecCount;
@@ -2468,6 +2632,7 @@
         void resetSafely(long now) {
             mDurationsTable = null;
             mDurationsTableSize = 0;
+            mRunCount = mRunState != STATE_NOTHING ? 1 : 0;
             mStartedCount = mStartedState != STATE_NOTHING ? 1 : 0;
             mBoundCount = mBoundState != STATE_NOTHING ? 1 : 0;
             mExecCount = mExecState != STATE_NOTHING ? 1 : 0;
@@ -2481,6 +2646,7 @@
                         + printLongOffset(mDurationsTable[i]));
                 out.writeInt(mDurationsTable[i]);
             }
+            out.writeInt(mRunCount);
             out.writeInt(mStartedCount);
             out.writeInt(mBoundCount);
             out.writeInt(mExecCount);
@@ -2493,6 +2659,7 @@
                 return false;
             }
             mDurationsTableSize = mDurationsTable != null ? mDurationsTable.length : 0;
+            mRunCount = in.readInt();
             mStartedCount = in.readInt();
             mBoundCount = in.readInt();
             mExecCount = in.readInt();
@@ -2518,6 +2685,10 @@
         }
 
         void commitStateTime(long now) {
+            if (mRunState != STATE_NOTHING) {
+                addStateTime(SERVICE_RUN + (mRunState*SERVICE_COUNT), now - mRunStartTime);
+                mRunStartTime = now;
+            }
             if (mStartedState != STATE_NOTHING) {
                 addStateTime(SERVICE_STARTED + (mStartedState*SERVICE_COUNT),
                         now - mStartedStartTime);
@@ -2533,6 +2704,21 @@
             }
         }
 
+        private void updateRunning(int memFactor, long now) {
+            final int state = (mStartedState != STATE_NOTHING || mBoundState != STATE_NOTHING
+                    || mExecState != STATE_NOTHING) ? memFactor : STATE_NOTHING;
+            if (mRunState != state) {
+                if (mRunState != STATE_NOTHING) {
+                    addStateTime(SERVICE_RUN + (mRunState*SERVICE_COUNT),
+                            now - mRunStartTime);
+                } else if (state != STATE_NOTHING) {
+                    mRunCount++;
+                }
+                mRunState = state;
+                mRunStartTime = now;
+            }
+        }
+
         public void setStarted(boolean started, int memFactor, long now) {
             if (mActive <= 0) {
                 throw new IllegalStateException("Service " + this + " has mActive=" + mActive);
@@ -2556,6 +2742,7 @@
                         mProc.decStartedServices(memFactor, now);
                     }
                 }
+                updateRunning(memFactor, now);
             }
         }
 
@@ -2573,6 +2760,7 @@
                 }
                 mBoundState = state;
                 mBoundStartTime = now;
+                updateRunning(memFactor, now);
             }
         }
 
@@ -2589,6 +2777,7 @@
                 }
                 mExecState = state;
                 mExecStartTime = now;
+                updateRunning(memFactor, now);
             }
         }
 
diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java
index 13101512..356401c 100644
--- a/core/java/com/android/internal/widget/SubtitleView.java
+++ b/core/java/com/android/internal/widget/SubtitleView.java
@@ -74,6 +74,10 @@
     private float mSpacingAdd = 0;
     private int mInnerPaddingX = 0;
 
+    public SubtitleView(Context context) {
+        this(context, null);
+    }
+
     public SubtitleView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
diff --git a/media/java/android/media/SubtitleController.java b/media/java/android/media/SubtitleController.java
index 2cf1b2d..e83c5ba 100644
--- a/media/java/android/media/SubtitleController.java
+++ b/media/java/android/media/SubtitleController.java
@@ -20,8 +20,7 @@
 import java.util.Vector;
 
 import android.content.Context;
-import android.media.MediaPlayer.OnSubtitleDataListener;
-import android.view.View;
+import android.media.SubtitleTrack.RenderingWidget;
 import android.view.accessibility.CaptioningManager;
 
 /**
@@ -32,7 +31,6 @@
  * @hide
  */
 public class SubtitleController {
-    private Context mContext;
     private MediaTimeProvider mTimeProvider;
     private Vector<Renderer> mRenderers;
     private Vector<SubtitleTrack> mTracks;
@@ -50,7 +48,6 @@
             Context context,
             MediaTimeProvider timeProvider,
             Listener listener) {
-        mContext = context;
         mTimeProvider = timeProvider;
         mListener = listener;
 
@@ -79,11 +76,11 @@
         return mSelectedTrack;
     }
 
-    private View getSubtitleView() {
+    private RenderingWidget getRenderingWidget() {
         if (mSelectedTrack == null) {
             return null;
         }
-        return mSelectedTrack.getView();
+        return mSelectedTrack.getRenderingWidget();
     }
 
     /**
@@ -110,7 +107,7 @@
         }
 
         mSelectedTrack = track;
-        mAnchor.setSubtitleView(getSubtitleView());
+        mAnchor.setSubtitleWidget(getRenderingWidget());
 
         if (mSelectedTrack != null) {
             mSelectedTrack.setTimeProvider(mTimeProvider);
@@ -268,17 +265,16 @@
     }
 
     /**
-     * Subtitle anchor, an object that is able to display a subtitle view,
+     * Subtitle anchor, an object that is able to display a subtitle renderer,
      * e.g. a VideoView.
      */
     public interface Anchor {
         /**
-         * Anchor should set the subtitle view to the supplied view,
-         * or none, if the supplied view is null.
-         *
-         * @param view subtitle view, or null
+         * Anchor should use the supplied subtitle rendering widget, or
+         * none if it is null.
+         * @hide
          */
-        public void setSubtitleView(View view);
+        public void setSubtitleWidget(RenderingWidget subtitleWidget);
     }
 
     private Anchor mAnchor;
@@ -290,11 +286,11 @@
         }
 
         if (mAnchor != null) {
-            mAnchor.setSubtitleView(null);
+            mAnchor.setSubtitleWidget(null);
         }
         mAnchor = anchor;
         if (mAnchor != null) {
-            mAnchor.setSubtitleView(getSubtitleView());
+            mAnchor.setSubtitleWidget(getRenderingWidget());
         }
     }
 
diff --git a/media/java/android/media/SubtitleTrack.java b/media/java/android/media/SubtitleTrack.java
index 09fb3f2..cb689af 100644
--- a/media/java/android/media/SubtitleTrack.java
+++ b/media/java/android/media/SubtitleTrack.java
@@ -16,11 +16,11 @@
 
 package android.media;
 
+import android.graphics.Canvas;
 import android.os.Handler;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Pair;
-import android.view.View;
 
 import java.util.Iterator;
 import java.util.NoSuchElementException;
@@ -98,16 +98,16 @@
     public abstract void onData(String data, boolean eos, long runID);
 
     /**
-     * Called when adding the subtitle rendering view to the view hierarchy, as
-     * well as when showing or hiding the subtitle track, or when the video
+     * Called when adding the subtitle rendering widget to the view hierarchy,
+     * as well as when showing or hiding the subtitle track, or when the video
      * surface position has changed.
      *
-     * @return the view object that displays this subtitle track.  For most
-     * renderers there should be a single shared view instance that is used
-     * for all tracks supported by that renderer, as at most one subtitle
-     * track is visible at one time.
+     * @return the widget that renders this subtitle track. For most renderers
+     *         there should be a single shared instance that is used for all
+     *         tracks supported by that renderer, as at most one subtitle track
+     *         is visible at one time.
      */
-    public abstract View getView();
+    public abstract RenderingWidget getRenderingWidget();
 
     /**
      * Called when the active cues have changed, and the contents of the subtitle
@@ -268,7 +268,7 @@
         }
 
         mVisible = true;
-        getView().setVisibility(View.VISIBLE);
+        getRenderingWidget().setVisible(true);
         if (mTimeProvider != null) {
             mTimeProvider.scheduleUpdate(this);
         }
@@ -283,7 +283,7 @@
         if (mTimeProvider != null) {
             mTimeProvider.cancelNotifications(this);
         }
-        getView().setVisibility(View.INVISIBLE);
+        getRenderingWidget().setVisible(false);
         mVisible = false;
     }
 
@@ -645,4 +645,61 @@
             }
         }
     }
+
+    /**
+     * Interface for rendering subtitles onto a Canvas.
+     */
+    public interface RenderingWidget {
+        /**
+         * Sets the widget's callback, which is used to send updates when the
+         * rendered data has changed.
+         *
+         * @param callback update callback
+         */
+        public void setOnChangedListener(OnChangedListener callback);
+
+        /**
+         * Sets the widget's size.
+         *
+         * @param width width in pixels
+         * @param height height in pixels
+         */
+        public void setSize(int width, int height);
+
+        /**
+         * Sets whether the widget should draw subtitles.
+         *
+         * @param visible true if subtitles should be drawn, false otherwise
+         */
+        public void setVisible(boolean visible);
+
+        /**
+         * Renders subtitles onto a {@link Canvas}.
+         *
+         * @param c canvas on which to render subtitles
+         */
+        public void draw(Canvas c);
+
+        /**
+         * Called when the widget is attached to a window.
+         */
+        public void onAttachedToWindow();
+
+        /**
+         * Called when the widget is detached from a window.
+         */
+        public void onDetachedFromWindow();
+
+        /**
+         * Callback used to send updates about changes to rendering data.
+         */
+        public interface OnChangedListener {
+            /**
+             * Called when the rendering data has changed.
+             *
+             * @param renderingWidget the widget whose data has changed
+             */
+            public void onChanged(RenderingWidget renderingWidget);
+        }
+    }
 }
diff --git a/media/java/android/media/WebVttRenderer.java b/media/java/android/media/WebVttRenderer.java
index 527c57f..74773a8 100644
--- a/media/java/android/media/WebVttRenderer.java
+++ b/media/java/android/media/WebVttRenderer.java
@@ -1,12 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package android.media;
 
 import android.content.Context;
+import android.text.SpannableStringBuilder;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.TextView;
+import android.view.ViewGroup;
+import android.view.accessibility.CaptioningManager;
+import android.view.accessibility.CaptioningManager.CaptionStyle;
+import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
+import android.widget.LinearLayout;
 
+import com.android.internal.widget.SubtitleView;
+
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
@@ -14,10 +39,12 @@
 
 /** @hide */
 public class WebVttRenderer extends SubtitleController.Renderer {
-    private TextView mMyTextView;
+    private final Context mContext;
 
-    public WebVttRenderer(Context context, AttributeSet attrs) {
-        mMyTextView = new WebVttView(context, attrs);
+    private WebVttRenderingWidget mRenderingWidget;
+
+    public WebVttRenderer(Context context) {
+        mContext = context;
     }
 
     @Override
@@ -30,19 +57,11 @@
 
     @Override
     public SubtitleTrack createTrack(MediaFormat format) {
-        return new WebVttTrack(format, mMyTextView);
-    }
-}
+        if (mRenderingWidget == null) {
+            mRenderingWidget = new WebVttRenderingWidget(mContext);
+        }
 
-/** @hide */
-class WebVttView extends TextView {
-    public WebVttView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setTextColor(0xffffff00);
-        setTextSize(46);
-        setTextAlignment(TextView.TEXT_ALIGNMENT_CENTER);
-        setLayoutParams(new LayoutParams(
-                LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
+        return new WebVttTrack(mRenderingWidget, format);
     }
 }
 
@@ -954,26 +973,26 @@
 class WebVttTrack extends SubtitleTrack implements WebVttCueListener {
     private static final String TAG = "WebVttTrack";
 
-    private final TextView mTextView;
-
     private final WebVttParser mParser = new WebVttParser(this);
     private final UnstyledTextExtractor mExtractor =
         new UnstyledTextExtractor();
     private final Tokenizer mTokenizer = new Tokenizer(mExtractor);
     private final Vector<Long> mTimestamps = new Vector<Long>();
+    private final WebVttRenderingWidget mRenderingWidget;
 
     private final Map<String, TextTrackRegion> mRegions =
         new HashMap<String, TextTrackRegion>();
     private Long mCurrentRunID;
 
-    WebVttTrack(MediaFormat format, TextView textView) {
+    WebVttTrack(WebVttRenderingWidget renderingWidget, MediaFormat format) {
         super(format);
-        mTextView = textView;
+
+        mRenderingWidget = renderingWidget;
     }
 
     @Override
-    public View getView() {
-        return mTextView;
+    public WebVttRenderingWidget getRenderingWidget() {
+        return mRenderingWidget;
     }
 
     @Override
@@ -1051,6 +1070,7 @@
         }
     }
 
+    @Override
     public void updateView(Vector<SubtitleTrack.Cue> activeCues) {
         if (!mVisible) {
             // don't keep the state if we are not visible
@@ -1066,29 +1086,737 @@
                 Log.d(TAG, "at (illegal state) the active cues are:");
             }
         }
-        StringBuilder text = new StringBuilder();
-        StringBuilder lineBuilder = new StringBuilder();
-        for (Cue o: activeCues) {
-            TextTrackCue cue = (TextTrackCue)o;
-            if (DEBUG) Log.d(TAG, cue.toString());
-            for (TextTrackCueSpan[] line: cue.mLines) {
-                for (TextTrackCueSpan span: line) {
-                    if (!span.mEnabled) {
-                        continue;
-                    }
-                    lineBuilder.append(span.mText);
+
+        mRenderingWidget.setActiveCues(activeCues);
+    }
+}
+
+/**
+ * Widget capable of rendering WebVTT captions.
+ *
+ * @hide
+ */
+class WebVttRenderingWidget extends ViewGroup implements SubtitleTrack.RenderingWidget {
+    private static final boolean DEBUG = false;
+    private static final int DEBUG_REGION_BACKGROUND = 0x800000FF;
+    private static final int DEBUG_CUE_BACKGROUND = 0x80FF0000;
+
+    /** WebVtt specifies line height as 5.3% of the viewport height. */
+    private static final float LINE_HEIGHT_RATIO = 0.0533f;
+
+    /** Map of active regions, used to determine enter/exit. */
+    private final ArrayMap<TextTrackRegion, RegionLayout> mRegionBoxes =
+            new ArrayMap<TextTrackRegion, RegionLayout>();
+
+    /** Map of active cues, used to determine enter/exit. */
+    private final ArrayMap<TextTrackCue, CueLayout> mCueBoxes =
+            new ArrayMap<TextTrackCue, CueLayout>();
+
+    /** Captioning manager, used to obtain and track caption properties. */
+    private final CaptioningManager mManager;
+
+    /** Callback for rendering changes. */
+    private OnChangedListener mListener;
+
+    /** Current caption style. */
+    private CaptionStyle mCaptionStyle;
+
+    /** Current font size, computed from font scaling factor and height. */
+    private float mFontSize;
+
+    /** Whether a caption style change listener is registered. */
+    private boolean mHasChangeListener;
+
+    public WebVttRenderingWidget(Context context) {
+        this(context, null);
+    }
+
+    public WebVttRenderingWidget(Context context, AttributeSet attrs) {
+        this(context, null, 0);
+    }
+
+    public WebVttRenderingWidget(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        // Cannot render text over video when layer type is hardware.
+        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+        mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
+        mCaptionStyle = mManager.getUserStyle();
+        mFontSize = mManager.getFontScale() * getHeight() * LINE_HEIGHT_RATIO;
+    }
+
+    @Override
+    public void setSize(int width, int height) {
+        final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+        final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+
+        measure(widthSpec, heightSpec);
+        layout(0, 0, width, height);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        manageChangeListener();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        manageChangeListener();
+    }
+
+    @Override
+    public void setOnChangedListener(OnChangedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            setVisibility(View.VISIBLE);
+        } else {
+            setVisibility(View.GONE);
+        }
+
+        manageChangeListener();
+    }
+
+    /**
+     * Manages whether this renderer is listening for caption style changes.
+     */
+    private void manageChangeListener() {
+        final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE;
+        if (mHasChangeListener != needsListener) {
+            mHasChangeListener = needsListener;
+
+            if (needsListener) {
+                mManager.addCaptioningChangeListener(mCaptioningListener);
+
+                final CaptionStyle captionStyle = mManager.getUserStyle();
+                final float fontSize = mManager.getFontScale() * getHeight() * LINE_HEIGHT_RATIO;
+                setCaptionStyle(captionStyle, fontSize);
+            } else {
+                mManager.removeCaptioningChangeListener(mCaptioningListener);
+            }
+        }
+    }
+
+    public void setActiveCues(Vector<SubtitleTrack.Cue> activeCues) {
+        final Context context = getContext();
+        final CaptionStyle captionStyle = mCaptionStyle;
+        final float fontSize = mFontSize;
+
+        prepForPrune();
+
+        // Ensure we have all necessary cue and region boxes.
+        final int count = activeCues.size();
+        for (int i = 0; i < count; i++) {
+            final TextTrackCue cue = (TextTrackCue) activeCues.get(i);
+            final TextTrackRegion region = cue.mRegion;
+            if (region != null) {
+                RegionLayout regionBox = mRegionBoxes.get(region);
+                if (regionBox == null) {
+                    regionBox = new RegionLayout(context, region, captionStyle, fontSize);
+                    mRegionBoxes.put(region, regionBox);
+                    addView(regionBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
                 }
-                if (lineBuilder.length() > 0) {
-                    text.append(lineBuilder.toString()).append("\n");
-                    lineBuilder.delete(0, lineBuilder.length());
+                regionBox.put(cue);
+            } else {
+                CueLayout cueBox = mCueBoxes.get(cue);
+                if (cueBox == null) {
+                    cueBox = new CueLayout(context, cue, captionStyle, fontSize);
+                    mCueBoxes.put(cue, cueBox);
+                    addView(cueBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+                }
+                cueBox.update();
+                cueBox.setOrder(i);
+            }
+        }
+
+        prune();
+
+        // Force measurement and layout.
+        final int width = getWidth();
+        final int height = getHeight();
+        setSize(width, height);
+
+        if (mListener != null) {
+            mListener.onChanged(this);
+        }
+    }
+
+    private void setCaptionStyle(CaptionStyle captionStyle, float fontSize) {
+        mCaptionStyle = captionStyle;
+        mFontSize = fontSize;
+
+        final int cueCount = mCueBoxes.size();
+        for (int i = 0; i < cueCount; i++) {
+            final CueLayout cueBox = mCueBoxes.valueAt(i);
+            cueBox.setCaptionStyle(captionStyle, fontSize);
+        }
+
+        final int regionCount = mRegionBoxes.size();
+        for (int i = 0; i < regionCount; i++) {
+            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
+            regionBox.setCaptionStyle(captionStyle, fontSize);
+        }
+    }
+
+    /**
+     * Remove inactive cues and regions.
+     */
+    private void prune() {
+        int regionCount = mRegionBoxes.size();
+        for (int i = 0; i < regionCount; i++) {
+            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
+            if (regionBox.prune()) {
+                removeView(regionBox);
+                mRegionBoxes.removeAt(i);
+                regionCount--;
+                i--;
+            }
+        }
+
+        int cueCount = mCueBoxes.size();
+        for (int i = 0; i < cueCount; i++) {
+            final CueLayout cueBox = mCueBoxes.valueAt(i);
+            if (!cueBox.isActive()) {
+                removeView(cueBox);
+                mCueBoxes.removeAt(i);
+                cueCount--;
+                i--;
+            }
+        }
+    }
+
+    /**
+     * Reset active cues and regions.
+     */
+    private void prepForPrune() {
+        final int regionCount = mRegionBoxes.size();
+        for (int i = 0; i < regionCount; i++) {
+            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
+            regionBox.prepForPrune();
+        }
+
+        final int cueCount = mCueBoxes.size();
+        for (int i = 0; i < cueCount; i++) {
+            final CueLayout cueBox = mCueBoxes.valueAt(i);
+            cueBox.prepForPrune();
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        final int regionCount = mRegionBoxes.size();
+        for (int i = 0; i < regionCount; i++) {
+            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
+            regionBox.measureForParent(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        final int cueCount = mCueBoxes.size();
+        for (int i = 0; i < cueCount; i++) {
+            final CueLayout cueBox = mCueBoxes.valueAt(i);
+            cueBox.measureForParent(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int viewportWidth = r - l;
+        final int viewportHeight = b - t;
+
+        setCaptionStyle(mCaptionStyle,
+                mManager.getFontScale() * LINE_HEIGHT_RATIO * viewportHeight);
+
+        final int regionCount = mRegionBoxes.size();
+        for (int i = 0; i < regionCount; i++) {
+            final RegionLayout regionBox = mRegionBoxes.valueAt(i);
+            layoutRegion(viewportWidth, viewportHeight, regionBox);
+        }
+
+        final int cueCount = mCueBoxes.size();
+        for (int i = 0; i < cueCount; i++) {
+            final CueLayout cueBox = mCueBoxes.valueAt(i);
+            layoutCue(viewportWidth, viewportHeight, cueBox);
+        }
+    }
+
+    /**
+     * Lays out a region within the viewport. The region handles layout for
+     * contained cues.
+     */
+    private void layoutRegion(
+            int viewportWidth, int viewportHeight,
+            RegionLayout regionBox) {
+        final TextTrackRegion region = regionBox.getRegion();
+        final int regionHeight = regionBox.getMeasuredHeight();
+        final int regionWidth = regionBox.getMeasuredWidth();
+
+        // TODO: Account for region anchor point.
+        final float x = region.mViewportAnchorPointX;
+        final float y = region.mViewportAnchorPointY;
+        final int left = (int) (x * (viewportWidth - regionWidth) / 100);
+        final int top = (int) (y * (viewportHeight - regionHeight) / 100);
+
+        regionBox.layout(left, top, left + regionWidth, top + regionHeight);
+    }
+
+    /**
+     * Lays out a cue within the viewport.
+     */
+    private void layoutCue(
+            int viewportWidth, int viewportHeight, CueLayout cueBox) {
+        final TextTrackCue cue = cueBox.getCue();
+        final int direction = getLayoutDirection();
+        final int absAlignment = resolveCueAlignment(direction, cue.mAlignment);
+        final boolean cueSnapToLines = cue.mSnapToLines;
+
+        int size = 100 * cueBox.getMeasuredWidth() / viewportWidth;
+
+        // Determine raw x-position.
+        int xPosition;
+        switch (absAlignment) {
+            case TextTrackCue.ALIGNMENT_LEFT:
+                xPosition = cue.mTextPosition;
+                break;
+            case TextTrackCue.ALIGNMENT_RIGHT:
+                xPosition = cue.mTextPosition - size;
+                break;
+            case TextTrackCue.ALIGNMENT_MIDDLE:
+            default:
+                xPosition = cue.mTextPosition - size / 2;
+                break;
+        }
+
+        // Adjust x-position for layout.
+        if (direction == LAYOUT_DIRECTION_RTL) {
+            xPosition = 100 - xPosition;
+        }
+
+        // If the text track cue snap-to-lines flag is set, adjust
+        // x-position and size for padding. This is equivalent to placing the
+        // cue within the title-safe area.
+        if (cueSnapToLines) {
+            final int paddingLeft = 100 * getPaddingLeft() / viewportWidth;
+            final int paddingRight = 100 * getPaddingRight() / viewportWidth;
+            if (xPosition < paddingLeft && xPosition + size > paddingLeft) {
+                xPosition += paddingLeft;
+                size -= paddingLeft;
+            }
+            final float rightEdge = 100 - paddingRight;
+            if (xPosition < rightEdge && xPosition + size > rightEdge) {
+                size -= paddingRight;
+            }
+        }
+
+        // Compute absolute left position and width.
+        final int left = xPosition * viewportWidth / 100;
+        final int width = size * viewportWidth / 100;
+
+        // Determine initial y-position.
+        final int yPosition = calculateLinePosition(cueBox);
+
+        // Compute absolute final top position and height.
+        final int height = cueBox.getMeasuredHeight();
+        final int top;
+        if (yPosition < 0) {
+            // TODO: This needs to use the actual height of prior boxes.
+            top = viewportHeight + yPosition * height;
+        } else {
+            top = yPosition * (viewportHeight - height) / 100;
+        }
+
+        // Layout cue in final position.
+        cueBox.layout(left, top, left + width, top + height);
+    }
+
+    /**
+     * Calculates the line position for a cue.
+     * <p>
+     * If the resulting position is negative, it represents a bottom-aligned
+     * position relative to the number of active cues. Otherwise, it represents
+     * a percentage [0-100] of the viewport height.
+     */
+    private int calculateLinePosition(CueLayout cueBox) {
+        final TextTrackCue cue = cueBox.getCue();
+        final Integer linePosition = cue.mLinePosition;
+        final boolean snapToLines = cue.mSnapToLines;
+        final boolean autoPosition = (linePosition == null);
+
+        if (!snapToLines && !autoPosition && (linePosition < 0 || linePosition > 100)) {
+            // Invalid line position defaults to 100.
+            return 100;
+        } else if (!autoPosition) {
+            // Use the valid, supplied line position.
+            return linePosition;
+        } else if (!snapToLines) {
+            // Automatic, non-snapped line position defaults to 100.
+            return 100;
+        } else {
+            // Automatic snapped line position uses active cue order.
+            return -(cueBox.mOrder + 1);
+        }
+    }
+
+    /**
+     * Resolves cue alignment according to the specified layout direction.
+     */
+    private static int resolveCueAlignment(int layoutDirection, int alignment) {
+        switch (alignment) {
+            case TextTrackCue.ALIGNMENT_START:
+                return layoutDirection == View.LAYOUT_DIRECTION_LTR ?
+                        TextTrackCue.ALIGNMENT_LEFT : TextTrackCue.ALIGNMENT_RIGHT;
+            case TextTrackCue.ALIGNMENT_END:
+                return layoutDirection == View.LAYOUT_DIRECTION_LTR ?
+                        TextTrackCue.ALIGNMENT_RIGHT : TextTrackCue.ALIGNMENT_LEFT;
+        }
+        return alignment;
+    }
+
+    private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() {
+        @Override
+        public void onFontScaleChanged(float fontScale) {
+            final float fontSize = fontScale * getHeight() * LINE_HEIGHT_RATIO;
+            setCaptionStyle(mCaptionStyle, fontSize);
+        }
+
+        @Override
+        public void onUserStyleChanged(CaptionStyle userStyle) {
+            setCaptionStyle(userStyle, mFontSize);
+        }
+    };
+
+    /**
+     * A text track region represents a portion of the video viewport and
+     * provides a rendering area for text track cues.
+     */
+    private static class RegionLayout extends LinearLayout {
+        private final ArrayList<CueLayout> mRegionCueBoxes = new ArrayList<CueLayout>();
+        private final TextTrackRegion mRegion;
+
+        private CaptionStyle mCaptionStyle;
+        private float mFontSize;
+
+        public RegionLayout(Context context, TextTrackRegion region, CaptionStyle captionStyle,
+                float fontSize) {
+            super(context);
+
+            mRegion = region;
+            mCaptionStyle = captionStyle;
+            mFontSize = fontSize;
+
+            // TODO: Add support for vertical text
+            setOrientation(VERTICAL);
+
+            if (DEBUG) {
+                setBackgroundColor(DEBUG_REGION_BACKGROUND);
+            }
+        }
+
+        public void setCaptionStyle(CaptionStyle captionStyle, float fontSize) {
+            mCaptionStyle = captionStyle;
+            mFontSize = fontSize;
+
+            final int cueCount = mRegionCueBoxes.size();
+            for (int i = 0; i < cueCount; i++) {
+                final CueLayout cueBox = mRegionCueBoxes.get(i);
+                cueBox.setCaptionStyle(captionStyle, fontSize);
+            }
+        }
+
+        /**
+         * Performs the parent's measurement responsibilities, then
+         * automatically performs its own measurement.
+         */
+        public void measureForParent(int widthMeasureSpec, int heightMeasureSpec) {
+            final TextTrackRegion region = mRegion;
+            final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
+            final int specHeight = MeasureSpec.getSize(heightMeasureSpec);
+            final int width = (int) region.mWidth;
+
+            // Determine the absolute maximum region size as the requested size.
+            final int size = width * specWidth / 100;
+
+            widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.AT_MOST);
+            measure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        /**
+         * Prepares this region for pruning by setting all tracks as inactive.
+         * <p>
+         * Tracks that are added or updated using {@link #put(TextTrackCue)}
+         * after this calling this method will be marked as active.
+         */
+        public void prepForPrune() {
+            final int cueCount = mRegionCueBoxes.size();
+            for (int i = 0; i < cueCount; i++) {
+                final CueLayout cueBox = mRegionCueBoxes.get(i);
+                cueBox.prepForPrune();
+            }
+        }
+
+        /**
+         * Adds a {@link TextTrackCue} to this region. If the track had already
+         * been added, updates its active state.
+         *
+         * @param cue
+         */
+        public void put(TextTrackCue cue) {
+            final int cueCount = mRegionCueBoxes.size();
+            for (int i = 0; i < cueCount; i++) {
+                final CueLayout cueBox = mRegionCueBoxes.get(i);
+                if (cueBox.getCue() == cue) {
+                    cueBox.update();
+                    return;
+                }
+            }
+
+            final CueLayout cueBox = new CueLayout(getContext(), cue, mCaptionStyle, mFontSize);
+            addView(cueBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+
+            if (getChildCount() > mRegion.mLines) {
+                removeViewAt(0);
+            }
+        }
+
+        /**
+         * Remove all inactive tracks from this region.
+         *
+         * @return true if this region is empty and should be pruned
+         */
+        public boolean prune() {
+            int cueCount = mRegionCueBoxes.size();
+            for (int i = 0; i < cueCount; i++) {
+                final CueLayout cueBox = mRegionCueBoxes.get(i);
+                if (!cueBox.isActive()) {
+                    mRegionCueBoxes.remove(i);
+                    removeView(cueBox);
+                    cueCount--;
+                    i--;
+                }
+            }
+
+            return mRegionCueBoxes.isEmpty();
+        }
+
+        /**
+         * @return the region data backing this layout
+         */
+        public TextTrackRegion getRegion() {
+            return mRegion;
+        }
+    }
+
+    /**
+     * A text track cue is the unit of time-sensitive data in a text track,
+     * corresponding for instance for subtitles and captions to the text that
+     * appears at a particular time and disappears at another time.
+     * <p>
+     * A single cue may contain multiple {@link SpanLayout}s, each representing a
+     * single line of text.
+     */
+    private static class CueLayout extends LinearLayout {
+        public final TextTrackCue mCue;
+
+        private CaptionStyle mCaptionStyle;
+        private float mFontSize;
+
+        private boolean mActive;
+        private int mOrder;
+
+        public CueLayout(
+                Context context, TextTrackCue cue, CaptionStyle captionStyle, float fontSize) {
+            super(context);
+
+            mCue = cue;
+            mCaptionStyle = captionStyle;
+            mFontSize = fontSize;
+
+            // TODO: Add support for vertical text.
+            final boolean horizontal = cue.mWritingDirection
+                    == TextTrackCue.WRITING_DIRECTION_HORIZONTAL;
+            setOrientation(horizontal ? VERTICAL : HORIZONTAL);
+
+            switch (cue.mAlignment) {
+                case TextTrackCue.ALIGNMENT_END:
+                    setGravity(Gravity.END);
+                    break;
+                case TextTrackCue.ALIGNMENT_LEFT:
+                    setGravity(Gravity.LEFT);
+                    break;
+                case TextTrackCue.ALIGNMENT_MIDDLE:
+                    setGravity(horizontal
+                            ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL);
+                    break;
+                case TextTrackCue.ALIGNMENT_RIGHT:
+                    setGravity(Gravity.RIGHT);
+                    break;
+                case TextTrackCue.ALIGNMENT_START:
+                    setGravity(Gravity.START);
+                    break;
+            }
+
+            if (DEBUG) {
+                setBackgroundColor(DEBUG_CUE_BACKGROUND);
+            }
+
+            update();
+        }
+
+        public void setCaptionStyle(CaptionStyle style, float fontSize) {
+            mCaptionStyle = style;
+            mFontSize = fontSize;
+
+            final int n = getChildCount();
+            for (int i = 0; i < n; i++) {
+                final View child = getChildAt(i);
+                if (child instanceof SpanLayout) {
+                    ((SpanLayout) child).setCaptionStyle(style, fontSize);
                 }
             }
         }
 
-        if (mTextView != null) {
-            if (DEBUG) Log.d(TAG, "updating to " + text.toString());
-            mTextView.setText(text.toString());
-            mTextView.postInvalidate();
+        public void prepForPrune() {
+            mActive = false;
+        }
+
+        public void update() {
+            mActive = true;
+
+            removeAllViews();
+
+            final CaptionStyle captionStyle = mCaptionStyle;
+            final float fontSize = mFontSize;
+            final TextTrackCueSpan[][] lines = mCue.mLines;
+            final int lineCount = lines.length;
+            for (int i = 0; i < lineCount; i++) {
+                final SpanLayout lineBox = new SpanLayout(getContext(), lines[i]);
+                lineBox.setCaptionStyle(captionStyle, fontSize);
+
+                addView(lineBox, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+            }
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        /**
+         * Performs the parent's measurement responsibilities, then
+         * automatically performs its own measurement.
+         */
+        public void measureForParent(int widthMeasureSpec, int heightMeasureSpec) {
+            final TextTrackCue cue = mCue;
+            final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
+            final int specHeight = MeasureSpec.getSize(heightMeasureSpec);
+            final int direction = getLayoutDirection();
+            final int absAlignment = resolveCueAlignment(direction, cue.mAlignment);
+
+            // Determine the maximum size of cue based on its starting position
+            // and the direction in which it grows.
+            final int maximumSize;
+            switch (absAlignment) {
+                case TextTrackCue.ALIGNMENT_LEFT:
+                    maximumSize = 100 - cue.mTextPosition;
+                    break;
+                case TextTrackCue.ALIGNMENT_RIGHT:
+                    maximumSize = cue.mTextPosition;
+                    break;
+                case TextTrackCue.ALIGNMENT_MIDDLE:
+                    if (cue.mTextPosition <= 50) {
+                        maximumSize = cue.mTextPosition * 2;
+                    } else {
+                        maximumSize = (100 - cue.mTextPosition) * 2;
+                    }
+                    break;
+                default:
+                    maximumSize = 0;
+            }
+
+            // Determine absolute maximum cue size as the smaller of the
+            // requested size and the maximum theoretical size.
+            final int size = Math.min(cue.mSize, maximumSize) * specWidth / 100;
+            widthMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(specHeight, MeasureSpec.AT_MOST);
+            measure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        /**
+         * Sets the order of this cue in the list of active cues.
+         *
+         * @param order the order of this cue in the list of active cues
+         */
+        public void setOrder(int order) {
+            mOrder = order;
+        }
+
+        /**
+         * @return whether this cue is marked as active
+         */
+        public boolean isActive() {
+            return mActive;
+        }
+
+        /**
+         * @return the cue data backing this layout
+         */
+        public TextTrackCue getCue() {
+            return mCue;
+        }
+    }
+
+    /**
+     * A text track line represents a single line of text within a cue.
+     * <p>
+     * A single line may contain multiple spans, each representing a section of
+     * text that may be enabled or disabled at a particular time.
+     */
+    private static class SpanLayout extends SubtitleView {
+        private final SpannableStringBuilder mBuilder = new SpannableStringBuilder();
+        private final TextTrackCueSpan[] mSpans;
+
+        public SpanLayout(Context context, TextTrackCueSpan[] spans) {
+            super(context);
+
+            mSpans = spans;
+
+            update();
+        }
+
+        public void update() {
+            final SpannableStringBuilder builder = mBuilder;
+            final TextTrackCueSpan[] spans = mSpans;
+
+            builder.clear();
+            builder.clearSpans();
+
+            final int spanCount = spans.length;
+            for (int i = 0; i < spanCount; i++) {
+                final TextTrackCueSpan span = spans[i];
+                if (span.mEnabled) {
+                    builder.append(spans[i].mText);
+                }
+            }
+
+            setText(builder);
+        }
+
+        public void setCaptionStyle(CaptionStyle captionStyle, float fontSize) {
+            setBackgroundColor(captionStyle.backgroundColor);
+            setForegroundColor(captionStyle.foregroundColor);
+            setEdgeColor(captionStyle.edgeColor);
+            setEdgeType(captionStyle.edgeType);
+            setTypeface(captionStyle.getTypeface());
+            setTextSize(fontSize);
         }
     }
 }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index b09dbb7..1e3fb40 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -1574,12 +1574,12 @@
             mSystemThread.installSystemApplicationInfo(info);
 
             synchronized (mSelf) {
-                ProcessRecord app = mSelf.newProcessRecordLocked(
-                        mSystemThread.getApplicationThread(), info,
+                ProcessRecord app = mSelf.newProcessRecordLocked(info,
                         info.processName, false);
                 app.persistent = true;
                 app.pid = MY_PID;
                 app.maxAdj = ProcessList.SYSTEM_ADJ;
+                app.makeActive(mSystemThread.getApplicationThread(), mSelf.mProcessStats);
                 mSelf.mProcessNames.put(app.processName, app.uid, app);
                 synchronized (mSelf.mPidsSelfLocked) {
                     mSelf.mPidsSelfLocked.put(app.pid, app);
@@ -2282,7 +2282,7 @@
         }
 
         if (app == null) {
-            app = newProcessRecordLocked(null, info, processName, isolated);
+            app = newProcessRecordLocked(info, processName, isolated);
             if (app == null) {
                 Slog.w(TAG, "Failed making new process record for "
                         + processName + "/" + info.uid + " isolated=" + isolated);
@@ -4488,7 +4488,7 @@
 
         EventLog.writeEvent(EventLogTags.AM_PROC_BOUND, app.userId, app.pid, app.processName);
 
-        app.thread = thread;
+        app.makeActive(thread, mProcessStats);
         app.curAdj = app.setAdj = -100;
         app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT;
         app.forcingToForeground = null;
@@ -7545,8 +7545,8 @@
     // GLOBAL MANAGEMENT
     // =========================================================
 
-    final ProcessRecord newProcessRecordLocked(IApplicationThread thread,
-            ApplicationInfo info, String customProcess, boolean isolated) {
+    final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
+            boolean isolated) {
         String proc = customProcess != null ? customProcess : info.processName;
         BatteryStatsImpl.Uid.Proc ps = null;
         BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
@@ -7574,8 +7574,7 @@
         synchronized (stats) {
             ps = stats.getProcessStatsLocked(info.uid, proc);
         }
-        return new ProcessRecord(ps, thread, info, proc, uid,
-                mProcessStats.getProcessStateLocked(info.packageName, info.uid, proc));
+        return new ProcessRecord(ps, info, proc, uid);
     }
 
     final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) {
@@ -7587,7 +7586,7 @@
         }
 
         if (app == null) {
-            app = newProcessRecordLocked(null, info, null, isolated);
+            app = newProcessRecordLocked(info, null, isolated);
             mProcessNames.put(info.processName, app.uid, app);
             if (isolated) {
                 mIsolatedProcesses.put(app.uid, app);
@@ -11789,7 +11788,7 @@
 
         app.resetPackageList(mProcessStats);
         app.unlinkDeathRecipient();
-        app.thread = null;
+        app.makeInactive(mProcessStats);
         app.forcingToForeground = null;
         app.foregroundServices = false;
         app.foregroundActivities = false;
@@ -14693,7 +14692,9 @@
     }
 
     private final void setProcessTrackerState(ProcessRecord proc, int memFactor, long now) {
-        proc.baseProcessTracker.setState(proc.repProcState, memFactor, now, proc.pkgList);
+        if (proc.thread != null) {
+            proc.baseProcessTracker.setState(proc.repProcState, memFactor, now, proc.pkgList);
+        }
     }
 
     private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index f1a030e..283d122 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -52,13 +52,13 @@
     final int uid;              // uid of process; may be different from 'info' if isolated
     final int userId;           // user of process.
     final String processName;   // name of the process
-    final ProcessStats.ProcessState baseProcessTracker;
     // List of packages running in the process
     final ArrayMap<String, ProcessStats.ProcessState> pkgList
             = new ArrayMap<String, ProcessStats.ProcessState>();
     IApplicationThread thread;  // the actual proc...  may be null only if
                                 // 'persistent' is true (in which case we
                                 // are in the process of launching the app)
+    ProcessStats.ProcessState baseProcessTracker;
     int pid;                    // The process of this application; 0 if none
     boolean starting;           // True if the process is being started
     long lastActivityTime;      // For managing the LRU list
@@ -349,18 +349,15 @@
         }
     }
     
-    ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, IApplicationThread _thread,
-            ApplicationInfo _info, String _processName, int _uid,
-            ProcessStats.ProcessState tracker) {
+    ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, ApplicationInfo _info,
+            String _processName, int _uid) {
         batteryStats = _batteryStats;
         info = _info;
         isolated = _info.uid != _uid;
         uid = _uid;
         userId = UserHandle.getUserId(_uid);
         processName = _processName;
-        baseProcessTracker = tracker;
-        pkgList.put(_info.packageName, tracker);
-        thread = _thread;
+        pkgList.put(_info.packageName, null);
         maxAdj = ProcessList.UNKNOWN_ADJ;
         curRawAdj = setRawAdj = -100;
         curAdj = setAdj = -100;
@@ -374,7 +371,53 @@
         shortStringName = null;
         stringName = null;
     }
-    
+
+    public void makeActive(IApplicationThread _thread, ProcessStatsService tracker) {
+        if (thread == null) {
+            final ProcessStats.ProcessState origBase = baseProcessTracker;
+            if (origBase != null) {
+                origBase.setState(ProcessStats.STATE_NOTHING,
+                        tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList);
+                origBase.makeInactive();
+            }
+            baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid,
+                    processName);
+            baseProcessTracker.makeActive();
+            for (int i=0; i<pkgList.size(); i++) {
+                ProcessStats.ProcessState ps = pkgList.valueAt(i);
+                if (ps != null && ps != origBase) {
+                    ps.makeInactive();
+                }
+                ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid, processName);
+                if (ps != baseProcessTracker) {
+                    ps.makeActive();
+                }
+                pkgList.setValueAt(i, ps);
+            }
+        }
+        thread = _thread;
+    }
+
+    public void makeInactive(ProcessStatsService tracker) {
+        if (thread != null) {
+            thread = null;
+            final ProcessStats.ProcessState origBase = baseProcessTracker;
+            if (origBase != null) {
+                origBase.setState(ProcessStats.STATE_NOTHING,
+                        tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList);
+                origBase.makeInactive();
+            }
+            baseProcessTracker = null;
+            for (int i=0; i<pkgList.size(); i++) {
+                ProcessStats.ProcessState ps = pkgList.valueAt(i);
+                if (ps != null && ps != origBase) {
+                    ps.makeInactive();
+                }
+                pkgList.setValueAt(i, null);
+            }
+        }
+    }
+
     /**
      * This method returns true if any of the activities within the process record are interesting
      * to the user. See HistoryRecord.isInterestingToUserLocked()
@@ -518,10 +561,22 @@
         long now = SystemClock.uptimeMillis();
         baseProcessTracker.setState(ProcessStats.STATE_NOTHING,
                 tracker.getMemFactorLocked(), now, pkgList);
-        if (pkgList.size() != 1) {
+        final int N = pkgList.size();
+        if (N != 1) {
+            for (int i=0; i<N; i++) {
+                ProcessStats.ProcessState ps = pkgList.valueAt(i);
+                if (ps != null && ps != baseProcessTracker) {
+                    ps.makeInactive();
+                }
+
+            }
             pkgList.clear();
-            pkgList.put(info.packageName, tracker.getProcessStateLocked(
-                    info.packageName, info.uid, processName));
+            ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
+                    info.packageName, info.uid, processName);
+            pkgList.put(info.packageName, ps);
+            if (thread != null && ps != baseProcessTracker) {
+                ps.makeActive();
+            }
         }
     }
     
diff --git a/services/java/com/android/server/am/ProcessStatsService.java b/services/java/com/android/server/am/ProcessStatsService.java
index 43ae46f..c180f6e 100644
--- a/services/java/com/android/server/am/ProcessStatsService.java
+++ b/services/java/com/android/server/am/ProcessStatsService.java
@@ -54,12 +54,12 @@
     // exists in and the offset into the array to find it.  The constants below
     // define the encoding of that data in an integer.
 
-    static final int MAX_HISTORIC_STATES = 4;   // Maximum number of historic states we will keep.
+    static final int MAX_HISTORIC_STATES = 6;   // Maximum number of historic states we will keep.
     static final String STATE_FILE_PREFIX = "state-"; // Prefix to use for state filenames.
     static final String STATE_FILE_SUFFIX = ".bin"; // Suffix to use for state filenames.
     static final String STATE_FILE_CHECKIN_SUFFIX = ".ci"; // State files that have checked in.
     static long WRITE_PERIOD = 30*60*1000;      // Write file every 30 minutes or so.
-    static long COMMIT_PERIOD = 24*60*60*1000;  // Commit current stats every day.
+    static long COMMIT_PERIOD = 12*60*60*1000;  // Commit current stats every 12 hours.
 
     final ActivityManagerService mAm;
     final File mBaseDir;
@@ -132,7 +132,7 @@
                     ArrayMap<String, ProcessStats.ServiceState> services = pkg.mServices;
                     for (int k=0; k<services.size(); k++) {
                         ProcessStats.ServiceState service = services.valueAt(k);
-                        if (service.isActive()) {
+                        if (service.isInUse()) {
                             if (service.mStartedState != ProcessStats.STATE_NOTHING) {
                                 service.setStarted(true, memFactor, now);
                             }
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 39756c3..8293bb8 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -318,6 +318,7 @@
         if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
             tracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
                     serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+            tracker.makeActive();
         }
         return tracker;
     }