Merge "Update the max height/width attribute of the TwoTargetPreference icon."
diff --git a/api/current.txt b/api/current.txt
index 02c313c..38bb68b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44133,7 +44133,6 @@
     field public static final int KEYBOARD_TAP = 3; // 0x3
     field public static final int LONG_PRESS = 0; // 0x0
     field public static final int VIRTUAL_KEY = 1; // 0x1
-    field public static final int VIRTUAL_KEY_RELEASE = 7; // 0x7
   }
 
   public class InflateException extends java.lang.RuntimeException {
@@ -71737,6 +71736,8 @@
 
   public class JSONException extends java.lang.Exception {
     ctor public JSONException(java.lang.String);
+    ctor public JSONException(java.lang.String, java.lang.Throwable);
+    ctor public JSONException(java.lang.Throwable);
   }
 
   public class JSONObject {
diff --git a/api/system-current.txt b/api/system-current.txt
index b9a8b20..d98319c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -47685,7 +47685,6 @@
     field public static final int KEYBOARD_TAP = 3; // 0x3
     field public static final int LONG_PRESS = 0; // 0x0
     field public static final int VIRTUAL_KEY = 1; // 0x1
-    field public static final int VIRTUAL_KEY_RELEASE = 7; // 0x7
   }
 
   public class InflateException extends java.lang.RuntimeException {
@@ -75662,6 +75661,8 @@
 
   public class JSONException extends java.lang.Exception {
     ctor public JSONException(java.lang.String);
+    ctor public JSONException(java.lang.String, java.lang.Throwable);
+    ctor public JSONException(java.lang.Throwable);
   }
 
   public class JSONObject {
diff --git a/api/test-current.txt b/api/test-current.txt
index 52f6439..94bfe84 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -44534,7 +44534,6 @@
     field public static final int KEYBOARD_TAP = 3; // 0x3
     field public static final int LONG_PRESS = 0; // 0x0
     field public static final int VIRTUAL_KEY = 1; // 0x1
-    field public static final int VIRTUAL_KEY_RELEASE = 7; // 0x7
   }
 
   public class InflateException extends java.lang.RuntimeException {
@@ -72175,6 +72174,8 @@
 
   public class JSONException extends java.lang.Exception {
     ctor public JSONException(java.lang.String);
+    ctor public JSONException(java.lang.String, java.lang.Throwable);
+    ctor public JSONException(java.lang.Throwable);
   }
 
   public class JSONObject {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 074cdef..cb648d5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -100,6 +100,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.LogPrinter;
+import android.util.LogWriter;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
@@ -125,6 +126,7 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.org.conscrypt.OpenSSLSocketImpl;
 import com.android.org.conscrypt.TrustedCertificateStore;
 
@@ -133,8 +135,6 @@
 import dalvik.system.VMDebug;
 import dalvik.system.VMRuntime;
 
-import com.google.android.collect.Lists;
-
 import libcore.io.DropBox;
 import libcore.io.EventLogger;
 import libcore.io.IoUtils;
@@ -152,6 +152,7 @@
 import java.net.InetAddress;
 import java.text.DateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -3052,7 +3053,7 @@
     public void handleInstallProvider(ProviderInfo info) {
         final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
         try {
-            installContentProviders(mInitialApplication, Lists.newArrayList(info));
+            installContentProviders(mInitialApplication, Arrays.asList(info));
         } finally {
             StrictMode.setThreadPolicy(oldPolicy);
         }
@@ -3935,6 +3936,14 @@
                 ActivityManager.getService().activityStopped(
                     activity.token, state, persistentState, description);
             } catch (RemoteException ex) {
+                // Dump statistics about bundle to help developers debug
+                final LogWriter writer = new LogWriter(Log.WARN, TAG);
+                final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+                pw.println("Bundle stats:");
+                Bundle.dumpStats(pw, state);
+                pw.println("PersistableBundle stats:");
+                Bundle.dumpStats(pw, persistentState);
+
                 if (ex instanceof TransactionTooLargeException
                         && activity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) {
                     Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex);
diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java
index 423ee52..ffd38e2 100644
--- a/core/java/android/app/admin/ConnectEvent.java
+++ b/core/java/android/app/admin/ConnectEvent.java
@@ -23,7 +23,11 @@
 import java.net.UnknownHostException;
 
 /**
- * A class that represents a connect library call event.
+ * A class that represents a TCP connect event initiated through the standard network stack.
+ *
+ * <p>It contains information about the originating app as well as the remote TCP endpoint.
+ *
+ * <p>Support both IPv4 and IPv6 connections.
  */
 public final class ConnectEvent extends NetworkEvent implements Parcelable {
 
diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java
index c21725f..f84c5b0 100644
--- a/core/java/android/app/admin/DnsEvent.java
+++ b/core/java/android/app/admin/DnsEvent.java
@@ -26,7 +26,10 @@
 import java.util.List;
 
 /**
- * A class that represents a DNS lookup event.
+ * A class that represents a DNS lookup event initiated through the standard network stack.
+ *
+ * <p>It contains information about the originating app as well as the DNS hostname and resolved
+ * IP addresses.
  */
 public final class DnsEvent extends NetworkEvent implements Parcelable {
 
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index 98bdde8..a6f6be2 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -45,6 +45,18 @@
     /** @hide */
     public static final int REASON_DEVICE_IDLE = 4;
 
+    /** @hide */
+    public static String getReasonName(int reason) {
+        switch (reason) {
+            case REASON_CANCELED: return "canceled";
+            case REASON_CONSTRAINTS_NOT_SATISFIED: return "constraints";
+            case REASON_PREEMPT: return "preempt";
+            case REASON_TIMEOUT: return "timeout";
+            case REASON_DEVICE_IDLE: return "device_idle";
+            default: return "unknown:" + reason;
+        }
+    }
+
     private final int jobId;
     private final PersistableBundle extras;
     private final Bundle transientExtras;
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 8d85880..a5763ef 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -21,6 +21,9 @@
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.Serializable;
 import java.util.ArrayList;
@@ -1555,4 +1558,49 @@
 
         mParcelledData = p;
     }
+
+    /** {@hide} */
+    public static void dumpStats(IndentingPrintWriter pw, String key, Object value) {
+        final Parcel tmp = Parcel.obtain();
+        tmp.writeValue(value);
+        final int size = tmp.dataPosition();
+        tmp.recycle();
+
+        // We only really care about logging large values
+        if (size > 1024) {
+            pw.println(key + " [size=" + size + "]");
+            if (value instanceof BaseBundle) {
+                dumpStats(pw, (BaseBundle) value);
+            } else if (value instanceof SparseArray) {
+                dumpStats(pw, (SparseArray) value);
+            }
+        }
+    }
+
+    /** {@hide} */
+    public static void dumpStats(IndentingPrintWriter pw, SparseArray array) {
+        pw.increaseIndent();
+        if (array == null) {
+            pw.println("[null]");
+            return;
+        }
+        for (int i = 0; i < array.size(); i++) {
+            dumpStats(pw, "0x" + Integer.toHexString(array.keyAt(i)), array.valueAt(i));
+        }
+        pw.decreaseIndent();
+    }
+
+    /** {@hide} */
+    public static void dumpStats(IndentingPrintWriter pw, BaseBundle bundle) {
+        pw.increaseIndent();
+        if (bundle == null) {
+            pw.println("[null]");
+            return;
+        }
+        final ArrayMap<String, Object> map = bundle.getMap();
+        for (int i = 0; i < map.size(); i++) {
+            dumpStats(pw, map.keyAt(i), map.valueAt(i));
+        }
+        pw.decreaseIndent();
+    }
 }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cf4fd99..a34668c 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -25,6 +25,7 @@
 import java.util.List;
 import java.util.Map;
 
+import android.app.job.JobParameters;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.telephony.SignalStrength;
@@ -238,6 +239,7 @@
     private static final String AGGREGATED_WAKELOCK_DATA = "awl";
     private static final String SYNC_DATA = "sy";
     private static final String JOB_DATA = "jb";
+    private static final String JOB_COMPLETION_DATA = "jbc";
     private static final String KERNEL_WAKELOCK_DATA = "kwl";
     private static final String WAKEUP_REASON_DATA = "wr";
     private static final String NETWORK_DATA = "nt";
@@ -498,6 +500,13 @@
         public abstract ArrayMap<String, ? extends Timer> getJobStats();
 
         /**
+         * Returns statistics about how jobs have completed.
+         *
+         * @return A Map of String job names to completion type -> count mapping.
+         */
+        public abstract ArrayMap<String, SparseIntArray> getJobCompletionStats();
+
+        /**
          * The statistics associated with a particular wake lock.
          */
         public static abstract class Wakelock {
@@ -3557,6 +3566,20 @@
                 }
             }
 
+            final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+            for (int ic=completions.size()-1; ic>=0; ic--) {
+                SparseIntArray types = completions.valueAt(ic);
+                if (types != null) {
+                    dumpLine(pw, uid, category, JOB_COMPLETION_DATA,
+                            "\"" + completions.keyAt(ic) + "\"",
+                            types.get(JobParameters.REASON_CANCELED, 0),
+                            types.get(JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED, 0),
+                            types.get(JobParameters.REASON_PREEMPT, 0),
+                            types.get(JobParameters.REASON_TIMEOUT, 0),
+                            types.get(JobParameters.REASON_DEVICE_IDLE, 0));
+                }
+            }
+
             dumpTimer(pw, uid, category, FLASHLIGHT_DATA, u.getFlashlightTurnedOnTimer(),
                     rawRealtime, which);
             dumpTimer(pw, uid, category, CAMERA_DATA, u.getCameraTurnedOnTimer(),
@@ -4979,6 +5002,25 @@
                 uidActivity = true;
             }
 
+            final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+            for (int ic=completions.size()-1; ic>=0; ic--) {
+                SparseIntArray types = completions.valueAt(ic);
+                if (types != null) {
+                    pw.print(prefix);
+                    pw.print("    Job Completions ");
+                    pw.print(completions.keyAt(ic));
+                    pw.print(":");
+                    for (int it=0; it<types.size(); it++) {
+                        pw.print(" ");
+                        pw.print(JobParameters.getReasonName(types.keyAt(it)));
+                        pw.print("(");
+                        pw.print(types.valueAt(it));
+                        pw.print("x)");
+                    }
+                    pw.println();
+                }
+            }
+
             uidActivity |= printTimer(pw, sb, u.getFlashlightTurnedOnTimer(), rawRealtime, which,
                     prefix, "Flashlight");
             uidActivity |= printTimer(pw, sb, u.getCameraTurnedOnTimer(), rawRealtime, which,
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 1c676b3..3f5b611 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -266,7 +266,13 @@
      * {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor
      * indicates that an object is not in close proximity.
      */
-    public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1;
+    public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1 << 0;
+
+    /**
+     * Flag for {@link WakeLock#release(int)} when called due to timeout.
+     * @hide
+     */
+    public static final int RELEASE_FLAG_TIMEOUT = 1 << 16;
 
     /**
      * Brightness value for fully on.
@@ -1242,7 +1248,8 @@
         private String mTag;
         private final String mPackageName;
         private final IBinder mToken;
-        private int mCount;
+        private int mInternalCount;
+        private int mExternalCount;
         private boolean mRefCounted = true;
         private boolean mHeld;
         private WorkSource mWorkSource;
@@ -1251,7 +1258,7 @@
 
         private final Runnable mReleaser = new Runnable() {
             public void run() {
-                release();
+                release(RELEASE_FLAG_TIMEOUT);
             }
         };
 
@@ -1328,7 +1335,9 @@
         }
 
         private void acquireLocked() {
-            if (!mRefCounted || mCount++ == 0) {
+            mInternalCount++;
+            mExternalCount++;
+            if (!mRefCounted || mInternalCount == 1) {
                 // Do this even if the wake lock is already thought to be held (mHeld == true)
                 // because non-reference counted wake locks are not always properly released.
                 // For example, the keyguard's wake lock might be forcibly released by the
@@ -1373,7 +1382,11 @@
          */
         public void release(int flags) {
             synchronized (mToken) {
-                if (!mRefCounted || --mCount == 0) {
+                mInternalCount--;
+                if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
+                    mExternalCount--;
+                }
+                if (!mRefCounted || mInternalCount == 0) {
                     mHandler.removeCallbacks(mReleaser);
                     if (mHeld) {
                         Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
@@ -1385,7 +1398,7 @@
                         mHeld = false;
                     }
                 }
-                if (mCount < 0) {
+                if (mRefCounted && mExternalCount < 0) {
                     throw new RuntimeException("WakeLock under-locked " + mTag);
                 }
             }
@@ -1469,7 +1482,7 @@
             synchronized (mToken) {
                 return "WakeLock{"
                     + Integer.toHexString(System.identityHashCode(this))
-                    + " held=" + mHeld + ", refCount=" + mCount + "}";
+                    + " held=" + mHeld + ", refCount=" + mInternalCount + "}";
             }
         }
 
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 0d81bd9..7a13ee8 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -488,8 +488,8 @@
      * Instructs the zygote to pre-load the classes and native libraries at the given paths
      * for the specified abi. Not all zygotes support this function.
      */
-    public void preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
-                                     String abi) throws ZygoteStartFailedEx, IOException {
+    public boolean preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
+                                        String abi) throws ZygoteStartFailedEx, IOException {
         synchronized(mLock) {
             ZygoteState state = openZygoteSocketIfNeeded(abi);
             state.writer.write("4");
@@ -508,6 +508,8 @@
             state.writer.newLine();
 
             state.writer.flush();
+
+            return (state.inputStream.readInt() == 0);
         }
     }
 
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index 0166c4a..81db2b7 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -119,6 +119,23 @@
     }
 
     /**
+     * @hide
+     * Remove a range of mappings as a batch.
+     *
+     * @param index Index to begin at
+     * @param size Number of mappings to remove
+     *
+     * <p>For indices outside of the range <code>0...size()-1</code>,
+     * the behavior is undefined.</p>
+     */
+    public void removeAtRange(int index, int size) {
+        size = Math.min(size, mSize - index);
+        System.arraycopy(mKeys, index + size, mKeys, index, mSize - (index + size));
+        System.arraycopy(mValues, index + size, mValues, index, mSize - (index + size));
+        mSize -= size;
+    }
+
+    /**
      * Removes the mapping at the given index.
      */
     public void removeAt(int index) {
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 0a73949..71a3f7e 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -58,6 +58,7 @@
 
     /**
      * The user has released a virtual or software keyboard key.
+     * @hide
      */
     public static final int VIRTUAL_KEY_RELEASE = 7;
 
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 181ad31..33e6521 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -833,25 +833,28 @@
                 if (!wrapContent) {
                     centerHorizontal(child, params, myWidth);
                 } else {
-                    params.mLeft = mPaddingLeft + params.leftMargin;
-                    params.mRight = params.mLeft + child.getMeasuredWidth();
+                    positionAtEdge(child, params, myWidth);
                 }
                 return true;
             } else {
                 // This is the default case. For RTL we start from the right and for LTR we start
                 // from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL.
-                if (isLayoutRtl()) {
-                    params.mRight = myWidth - mPaddingRight- params.rightMargin;
-                    params.mLeft = params.mRight - child.getMeasuredWidth();
-                } else {
-                    params.mLeft = mPaddingLeft + params.leftMargin;
-                    params.mRight = params.mLeft + child.getMeasuredWidth();
-                }
+                positionAtEdge(child, params, myWidth);
             }
         }
         return rules[ALIGN_PARENT_END] != 0;
     }
 
+    private void positionAtEdge(View child, LayoutParams params, int myWidth) {
+        if (isLayoutRtl()) {
+            params.mRight = myWidth - mPaddingRight - params.rightMargin;
+            params.mLeft = params.mRight - child.getMeasuredWidth();
+        } else {
+            params.mLeft = mPaddingLeft + params.leftMargin;
+            params.mRight = params.mLeft + child.getMeasuredWidth();
+        }
+    }
+
     private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
             boolean wrapContent) {
 
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index ece4981..54afc95 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -69,7 +69,10 @@
 import android.view.animation.Interpolator;
 import android.widget.AbsListView;
 import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
 import android.widget.ListView;
+import android.widget.Space;
+
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity.TargetInfo;
@@ -1412,6 +1415,10 @@
                 } else {
                     lp.height = v.getMeasuredHeight();
                 }
+                if (i != (mColumnCount - 1)) {
+                    row.addView(new Space(ChooserActivity.this),
+                            new LinearLayout.LayoutParams(0, 0, 1));
+                }
             }
 
             // Pre-measure so we can scale later.
@@ -1439,13 +1446,34 @@
             if (startType == ChooserListAdapter.TARGET_SERVICE) {
                 holder.row.setBackgroundColor(
                         getColor(R.color.chooser_service_row_background_color));
+                int nextStartType = mChooserListAdapter.getPositionTargetType(
+                        getFirstRowPosition(rowPosition + 1));
+                int serviceSpacing = holder.row.getContext().getResources()
+                        .getDimensionPixelSize(R.dimen.chooser_service_spacing);
+                int top = rowPosition == 0 ? serviceSpacing : 0;
+                if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
+                    setVertPadding(holder, top, serviceSpacing);
+                } else {
+                    setVertPadding(holder, top, 0);
+                }
             } else {
                 holder.row.setBackgroundColor(Color.TRANSPARENT);
+                int lastStartType = mChooserListAdapter.getPositionTargetType(
+                        getFirstRowPosition(rowPosition - 1));
+                if (lastStartType == ChooserListAdapter.TARGET_SERVICE || rowPosition == 0) {
+                    int serviceSpacing = holder.row.getContext().getResources()
+                            .getDimensionPixelSize(R.dimen.chooser_service_spacing);
+                    setVertPadding(holder, serviceSpacing, 0);
+                } else {
+                    setVertPadding(holder, 0, 0);
+                }
             }
 
             final int oldHeight = holder.row.getLayoutParams().height;
+            int measuredRowHeight = holder.measuredRowHeight + holder.row.getPaddingTop()
+                    + holder.row.getPaddingBottom();
             holder.row.getLayoutParams().height = Math.max(1,
-                    (int) (holder.measuredRowHeight * getRowScale(rowPosition)));
+                    (int) (measuredRowHeight * getRowScale(rowPosition)));
             if (holder.row.getLayoutParams().height != oldHeight) {
                 holder.row.requestLayout();
             }
@@ -1457,11 +1485,16 @@
                     holder.itemIndices[i] = start + i;
                     mChooserListAdapter.bindView(holder.itemIndices[i], v);
                 } else {
-                    v.setVisibility(View.GONE);
+                    v.setVisibility(View.INVISIBLE);
                 }
             }
         }
 
+        private void setVertPadding(RowViewHolder holder, int top, int bottom) {
+            holder.row.setPadding(holder.row.getPaddingLeft(), top,
+                    holder.row.getPaddingRight(), bottom);
+        }
+
         int getFirstRowPosition(int row) {
             final int callerCount = mChooserListAdapter.getCallerTargetCount();
             final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 04f7c76..09ad266 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -66,7 +66,7 @@
     void noteSyncStart(String name, int uid);
     void noteSyncFinish(String name, int uid);
     void noteJobStart(String name, int uid);
-    void noteJobFinish(String name, int uid);
+    void noteJobFinish(String name, int uid, int stopReason);
 
     void noteStartWakelock(int uid, int pid, String name, String historyName,
             int type, boolean unimportantForLogging);
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 79544df..ebea1a4 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -27,6 +27,7 @@
 import android.database.MatrixCursor.RowBuilder;
 import android.graphics.Point;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.FileObserver;
 import android.os.FileUtils;
@@ -156,11 +157,17 @@
         if (visibleFolder != null) {
             assert (visibleFolder.isDirectory());
 
-            final ContentResolver resolver = getContext().getContentResolver();
-            final Uri uri = MediaStore.Files.getDirectoryUri("external");
-            ContentValues values = new ContentValues();
-            values.put(MediaStore.Files.FileColumns.DATA, visibleFolder.getAbsolutePath());
-            resolver.insert(uri, values);
+            final long token = Binder.clearCallingIdentity();
+
+            try {
+                final ContentResolver resolver = getContext().getContentResolver();
+                final Uri uri = MediaStore.Files.getDirectoryUri("external");
+                ContentValues values = new ContentValues();
+                values.put(MediaStore.Files.FileColumns.DATA, visibleFolder.getAbsolutePath());
+                resolver.insert(uri, values);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
     }
 
@@ -214,22 +221,28 @@
         // They should be all null or not null at the same time. File#renameTo() doesn't work across
         // volumes so an exception will be thrown before calling this method.
         if (oldVisibleFile != null && newVisibleFile != null) {
-            final ContentResolver resolver = getContext().getContentResolver();
-            final Uri externalUri = newVisibleFile.isDirectory()
-                    ? MediaStore.Files.getDirectoryUri("external")
-                    : MediaStore.Files.getContentUri("external");
+            final long token = Binder.clearCallingIdentity();
 
-            ContentValues values = new ContentValues();
-            values.put(MediaStore.Files.FileColumns.DATA, newVisibleFile.getAbsolutePath());
+            try {
+                final ContentResolver resolver = getContext().getContentResolver();
+                final Uri externalUri = newVisibleFile.isDirectory()
+                        ? MediaStore.Files.getDirectoryUri("external")
+                        : MediaStore.Files.getContentUri("external");
 
-            // Logic borrowed from MtpDatabase.
-            // note - we are relying on a special case in MediaProvider.update() to update
-            // the paths for all children in the case where this is a directory.
-            final String path = oldVisibleFile.getAbsolutePath();
-            resolver.update(externalUri,
-                    values,
-                    "_data LIKE ? AND lower(_data)=lower(?)",
-                    new String[] { path, path });
+                ContentValues values = new ContentValues();
+                values.put(MediaStore.Files.FileColumns.DATA, newVisibleFile.getAbsolutePath());
+
+                // Logic borrowed from MtpDatabase.
+                // note - we are relying on a special case in MediaProvider.update() to update
+                // the paths for all children in the case where this is a directory.
+                final String path = oldVisibleFile.getAbsolutePath();
+                resolver.update(externalUri,
+                        values,
+                        "_data LIKE ? AND lower(_data)=lower(?)",
+                        new String[]{path, path});
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
     }
 
@@ -253,23 +266,29 @@
             throws FileNotFoundException {
         // visibleFolder is null if we're removing a document from external thumb drive or SD card.
         if (visibleFile != null) {
-            final ContentResolver resolver = getContext().getContentResolver();
-            final Uri externalUri = MediaStore.Files.getContentUri("external");
+            final long token = Binder.clearCallingIdentity();
 
-            // Remove media store entries for any files inside this directory, using
-            // path prefix match. Logic borrowed from MtpDatabase.
-            if (isFolder) {
-                final String path = visibleFile.getAbsolutePath() + "/";
+            try {
+                final ContentResolver resolver = getContext().getContentResolver();
+                final Uri externalUri = MediaStore.Files.getContentUri("external");
+
+                // Remove media store entries for any files inside this directory, using
+                // path prefix match. Logic borrowed from MtpDatabase.
+                if (isFolder) {
+                    final String path = visibleFile.getAbsolutePath() + "/";
+                    resolver.delete(externalUri,
+                            "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
+                            new String[]{path + "%", Integer.toString(path.length()), path});
+                }
+
+                // Remove media store entry for this exact file.
+                final String path = visibleFile.getAbsolutePath();
                 resolver.delete(externalUri,
-                        "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
-                        new String[] { path + "%", Integer.toString(path.length()), path });
+                        "_data LIKE ?1 AND lower(_data)=lower(?2)",
+                        new String[]{path, path});
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
-
-            // Remove media store entry for this exact file.
-            final String path = visibleFile.getAbsolutePath();
-            resolver.delete(externalUri,
-                    "_data LIKE ?1 AND lower(_data)=lower(?2)",
-                    new String[] { path, path });
         }
     }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index f3632f0..f085e29 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -840,7 +840,10 @@
             if (sipper.shouldHide) {
                 if (sipper.drainType != BatterySipper.DrainType.OVERCOUNTED
                         && sipper.drainType != BatterySipper.DrainType.SCREEN
-                        && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED) {
+                        && sipper.drainType != BatterySipper.DrainType.UNACCOUNTED
+                        && sipper.drainType != BatterySipper.DrainType.BLUETOOTH
+                        && sipper.drainType != BatterySipper.DrainType.WIFI
+                        && sipper.drainType != BatterySipper.DrainType.IDLE) {
                     // Don't add it if it is overcounted, unaccounted or screen
                     proportionalSmearPowerMah += sipper.totalPowerMah;
                 }
@@ -861,19 +864,19 @@
      * time.
      */
     public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
-        final long rawRealtimeMs = SystemClock.elapsedRealtime();
         long totalActivityTimeMs = 0;
         final SparseLongArray activityTimeArray = new SparseLongArray();
         for (int i = 0, size = sippers.size(); i < size; i++) {
             final BatteryStats.Uid uid = sippers.get(i).uidObj;
             if (uid != null) {
-                final long timeMs = getForegroundActivityTotalTimeMs(uid, rawRealtimeMs);
+                final long timeMs = getProcessForegroundTimeMs(uid,
+                        BatteryStats.STATS_SINCE_CHARGED);
                 activityTimeArray.put(uid.getUid(), timeMs);
                 totalActivityTimeMs += timeMs;
             }
         }
 
-        if (totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
+        if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
             final double screenPowerMah = screenSipper.totalPowerMah;
             for (int i = 0, size = sippers.size(); i < size; i++) {
                 final BatterySipper sipper = sippers.get(i);
@@ -936,17 +939,42 @@
         return false;
     }
 
+    public long convertUsToMs(long timeUs) {
+        return timeUs / 1000;
+    }
+
+    public long convertMsToUs(long timeMs) {
+        return timeMs * 1000;
+    }
+
     @VisibleForTesting
-    public long getForegroundActivityTotalTimeMs(BatteryStats.Uid uid, long rawRealtimeMs) {
+    public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
         final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
         if (timer != null) {
-            return timer.getTotalTimeLocked(rawRealtimeMs, BatteryStats.STATS_SINCE_CHARGED);
+            return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
         }
 
         return 0;
     }
 
     @VisibleForTesting
+    public long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) {
+        final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
+        final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP};
+
+        long timeUs = 0;
+        for (int type : foregroundTypes) {
+            final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which);
+            timeUs += localTime;
+        }
+
+        // Return the min value of STATE_TOP time and foreground activity time, since both of these
+        // time have some errors.
+        return convertUsToMs(
+                Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)));
+    }
+
+    @VisibleForTesting
     public void setPackageManager(PackageManager packageManager) {
         mPackageManager = packageManager;
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index cb00c5e..fa23e40 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -42,7 +42,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.os.WorkSource;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
@@ -75,7 +75,7 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
-import com.android.server.NetworkManagementSocketTagger;
+
 import libcore.util.EmptyArray;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -119,7 +119,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 159 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 160 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -194,6 +194,17 @@
         public String getSubsystemLowPowerStats();
     }
 
+    public static abstract class UserInfoProvider {
+        private int[] userIds;
+        protected abstract @Nullable int[] getUserIds();
+        private final void refreshUserIds() {
+            userIds = getUserIds();
+        }
+        private final boolean exists(int userId) {
+            return userIds != null ? ArrayUtils.contains(userIds, userId) : true;
+        }
+    }
+
     private final PlatformIdleStateCallback mPlatformIdleStateCallback;
 
     final class MyHandler extends Handler {
@@ -262,6 +273,7 @@
 
     public final MyHandler mHandler;
     private ExternalStatsSync mExternalSync = null;
+    private UserInfoProvider mUserInfoProvider = null;
 
     private BatteryCallback mCallback;
 
@@ -649,6 +661,7 @@
         mDailyFile = null;
         mHandler = null;
         mPlatformIdleStateCallback = null;
+        mUserInfoProvider = null;
         clearHistoryLocked();
     }
 
@@ -3664,11 +3677,11 @@
         addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_JOB_START, name, uid);
     }
 
-    public void noteJobFinishLocked(String name, int uid) {
+    public void noteJobFinishLocked(String name, int uid, int stopReason) {
         uid = mapUid(uid);
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
-        getUidStatsLocked(uid).noteStopJobLocked(name, elapsedRealtime);
+        getUidStatsLocked(uid).noteStopJobLocked(name, elapsedRealtime, stopReason);
         if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_FINISH, name, uid, 0)) {
             return;
         }
@@ -5702,6 +5715,11 @@
         final OverflowArrayMap<DualTimer> mJobStats;
 
         /**
+         * Count of the jobs that have completed and the reasons why they completed.
+         */
+        final ArrayMap<String, SparseIntArray> mJobCompletions = new ArrayMap<>();
+
+        /**
          * The statistics we have collected for this uid's sensor activations.
          */
         final SparseArray<Sensor> mSensorStats = new SparseArray<>();
@@ -5823,6 +5841,11 @@
         }
 
         @Override
+        public ArrayMap<String, SparseIntArray> getJobCompletionStats() {
+            return mJobCompletions;
+        }
+
+        @Override
         public SparseArray<? extends BatteryStats.Uid.Sensor> getSensorStats() {
             return mSensorStats;
         }
@@ -6706,6 +6729,7 @@
                 }
             }
             mJobStats.cleanup();
+            mJobCompletions.clear();
             for (int ise=mSensorStats.size()-1; ise>=0; ise--) {
                 Sensor s = mSensorStats.valueAt(ise);
                 if (s.reset()) {
@@ -6868,6 +6892,21 @@
             return !active;
         }
 
+        void writeJobCompletionsToParcelLocked(Parcel out) {
+            int NJC = mJobCompletions.size();
+            out.writeInt(NJC);
+            for (int ijc=0; ijc<NJC; ijc++) {
+                out.writeString(mJobCompletions.keyAt(ijc));
+                SparseIntArray types = mJobCompletions.valueAt(ijc);
+                int NT = types.size();
+                out.writeInt(NT);
+                for (int it=0; it<NT; it++) {
+                    out.writeInt(types.keyAt(it));
+                    out.writeInt(types.valueAt(it));
+                }
+            }
+        }
+
         void writeToParcelLocked(Parcel out, long uptimeUs, long elapsedRealtimeUs) {
             mOnBatteryBackgroundTimeBase.writeToParcel(out, uptimeUs, elapsedRealtimeUs);
             mOnBatteryScreenOffBackgroundTimeBase.writeToParcel(out, uptimeUs, elapsedRealtimeUs);
@@ -6899,6 +6938,8 @@
                 Timer.writeTimerToParcel(out, timer, elapsedRealtimeUs);
             }
 
+            writeJobCompletionsToParcelLocked(out);
+
             int NSE = mSensorStats.size();
             out.writeInt(NSE);
             for (int ise=0; ise<NSE; ise++) {
@@ -7114,6 +7155,24 @@
             }
         }
 
+        void readJobCompletionsFromParcelLocked(Parcel in) {
+            int numJobCompletions = in.readInt();
+            mJobCompletions.clear();
+            for (int j = 0; j < numJobCompletions; j++) {
+                String jobName = in.readString();
+                int numTypes = in.readInt();
+                if (numTypes > 0) {
+                    SparseIntArray types = new SparseIntArray();
+                    for (int k = 0; k < numTypes; k++) {
+                        int type = in.readInt();
+                        int count = in.readInt();
+                        types.put(type, count);
+                    }
+                    mJobCompletions.put(jobName, types);
+                }
+            }
+        }
+
         void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
             mOnBatteryBackgroundTimeBase.readFromParcel(in);
             mOnBatteryScreenOffBackgroundTimeBase.readFromParcel(in);
@@ -7148,6 +7207,8 @@
                 }
             }
 
+            readJobCompletionsFromParcelLocked(in);
+
             int numSensors = in.readInt();
             mSensorStats.clear();
             for (int k = 0; k < numSensors; k++) {
@@ -8460,11 +8521,20 @@
             }
         }
 
-        public void noteStopJobLocked(String name, long elapsedRealtimeMs) {
+        public void noteStopJobLocked(String name, long elapsedRealtimeMs, int stopReason) {
             DualTimer t = mJobStats.stopObject(name);
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
             }
+            if (mBsi.mOnBatteryTimeBase.isRunning()) {
+                SparseIntArray types = mJobCompletions.get(name);
+                if (types == null) {
+                    types = new SparseIntArray();
+                    mJobCompletions.put(name, types);
+                }
+                int last = types.get(stopReason, 0);
+                types.put(stopReason, last + 1);
+            }
         }
 
         public StopwatchTimer getWakelockTimerLocked(Wakelock wl, int type) {
@@ -8588,16 +8658,14 @@
         return mCpuFreqs;
     }
 
-    public BatteryStatsImpl(File systemDir, Handler handler) {
-        this(new SystemClocks(), systemDir, handler, null);
+    public BatteryStatsImpl(File systemDir, Handler handler, PlatformIdleStateCallback cb,
+            UserInfoProvider userInfoProvider) {
+        this(new SystemClocks(), systemDir, handler, cb, userInfoProvider);
     }
 
-    public BatteryStatsImpl(File systemDir, Handler handler, PlatformIdleStateCallback cb) {
-        this(new SystemClocks(), systemDir, handler, cb);
-    }
-
-    public BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
-            PlatformIdleStateCallback cb) {
+    private BatteryStatsImpl(Clocks clocks, File systemDir, Handler handler,
+            PlatformIdleStateCallback cb,
+            UserInfoProvider userInfoProvider) {
         init(clocks);
 
         if (systemDir != null) {
@@ -8684,6 +8752,7 @@
         clearHistoryLocked();
         updateDailyDeadlineLocked();
         mPlatformIdleStateCallback = cb;
+        mUserInfoProvider = userInfoProvider;
     }
 
     public BatteryStatsImpl(Parcel p) {
@@ -10172,6 +10241,7 @@
         // we read, we get a delta. If we are to distribute the cpu time, then do so. Otherwise
         // we just ignore the data.
         final long startTimeMs = mClocks.uptimeMillis();
+        mUserInfoProvider.refreshUserIds();
         mKernelUidCpuTimeReader.readDelta(!mOnBatteryInternal ? null :
                 new KernelUidCpuTimeReader.Callback() {
                     @Override
@@ -10185,6 +10255,11 @@
                                     + " no mapping to owning uid: " + uid);
                             return;
                         }
+                        if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
+                            Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
+                            mKernelUidCpuTimeReader.removeUid(uid);
+                            return;
+                        }
                         final Uid u = getUidStatsLocked(uid);
 
                         // Accumulate the total system and user time.
@@ -10357,6 +10432,11 @@
                                     + " no mapping to owning uid: " + uid);
                             return;
                         }
+                        if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
+                            Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
+                            mKernelUidCpuFreqTimeReader.removeUid(uid);
+                            return;
+                        }
                         final Uid u = getUidStatsLocked(uid);
                         if (u.mCpuFreqTimeMs == null) {
                             u.mCpuFreqTimeMs = new LongSamplingCounterArray(mOnBatteryTimeBase);
@@ -11018,6 +11098,23 @@
         return u;
     }
 
+    public void onCleanupUserLocked(int userId) {
+        final int firstUidForUser = UserHandle.getUid(userId, 0);
+        final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
+        mKernelUidCpuFreqTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+        mKernelUidCpuTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+    }
+
+    public void onUserRemovedLocked(int userId) {
+        final int firstUidForUser = UserHandle.getUid(userId, 0);
+        final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
+        mUidStats.put(firstUidForUser, null);
+        mUidStats.put(lastUidForUser, null);
+        final int firstIndex = mUidStats.indexOfKey(firstUidForUser);
+        final int lastIndex = mUidStats.indexOfKey(lastUidForUser);
+        mUidStats.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+    }
+
     /**
      * Remove the statistics object for a particular uid.
      */
@@ -11640,6 +11737,8 @@
                 u.readJobSummaryFromParcelLocked(name, in);
             }
 
+            u.readJobCompletionsFromParcelLocked(in);
+
             int NP = in.readInt();
             if (NP > 1000) {
                 throw new ParcelFormatException("File corrupt: too many sensors " + NP);
@@ -12079,6 +12178,8 @@
                 jobStats.valueAt(ij).writeSummaryFromParcelLocked(out, NOWREAL_SYS);
             }
 
+            u.writeJobCompletionsToParcelLocked(out);
+
             int NSE = u.mSensorStats.size();
             out.writeInt(NSE);
             for (int ise=0; ise<NSE; ise++) {
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index ce5e73a..f282fac 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -76,6 +76,17 @@
         mLastUidCpuFreqTimeMs.delete(uid);
     }
 
+    public void removeUidsInRange(int startUid, int endUid) {
+        if (endUid < startUid) {
+            return;
+        }
+        mLastUidCpuFreqTimeMs.put(startUid, null);
+        mLastUidCpuFreqTimeMs.put(endUid, null);
+        final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
+        final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
+        mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+    }
+
     @VisibleForTesting
     public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
         String line = reader.readLine();
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
index 181e1ac..37d9d1d 100644
--- a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
@@ -130,17 +130,42 @@
      * @param uid The UID to remove.
      */
     public void removeUid(int uid) {
-        int index = mLastUserTimeUs.indexOfKey(uid);
+        final int index = mLastSystemTimeUs.indexOfKey(uid);
         if (index >= 0) {
-            mLastUserTimeUs.removeAt(index);
             mLastSystemTimeUs.removeAt(index);
+            mLastUserTimeUs.removeAt(index);
         }
+        removeUidsFromKernelModule(uid, uid);
+    }
 
+    /**
+     * Removes UIDs in a given range from the kernel module and internal accounting data.
+     * @param startUid the first uid to remove
+     * @param endUid the last uid to remove
+     */
+    public void removeUidsInRange(int startUid, int endUid) {
+        if (endUid < startUid) {
+            return;
+        }
+        mLastSystemTimeUs.put(startUid, 0);
+        mLastUserTimeUs.put(startUid, 0);
+        mLastSystemTimeUs.put(endUid, 0);
+        mLastUserTimeUs.put(endUid, 0);
+        final int startIndex = mLastSystemTimeUs.indexOfKey(startUid);
+        final int endIndex = mLastSystemTimeUs.indexOfKey(endUid);
+        mLastSystemTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
+        mLastUserTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1);
+        removeUidsFromKernelModule(startUid, endUid);
+    }
+
+    private void removeUidsFromKernelModule(int startUid, int endUid) {
+        Slog.d(TAG, "Removing uids " + startUid + "-" + endUid);
         try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
-            writer.write(Integer.toString(uid) + "-" + Integer.toString(uid));
+            writer.write(startUid + "-" + endUid);
             writer.flush();
         } catch (IOException e) {
-            Slog.e(TAG, "failed to remove uid from uid_cputime module", e);
+            Slog.e(TAG, "failed to remove uids " + startUid + " - " + endUid
+                    + " from uid_cputime module", e);
         }
     }
 }
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index f4be128..66475e4 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -31,6 +31,7 @@
 import com.android.internal.logging.AndroidConfig;
 import com.android.server.NetworkManagementSocketTagger;
 import dalvik.system.VMRuntime;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.TimeZone;
@@ -228,8 +229,8 @@
      * @param argv Argument vector for main()
      * @param classLoader the classLoader to load {@className} with
      */
-    private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
-            throws Zygote.MethodAndArgsCaller {
+    private static Runnable findStaticMain(String className, String[] argv,
+            ClassLoader classLoader) {
         Class<?> cl;
 
         try {
@@ -263,7 +264,7 @@
          * clears up all the stack frames that were required in setting
          * up the process.
          */
-        throw new Zygote.MethodAndArgsCaller(m, argv);
+        return new MethodAndArgsCaller(m, argv);
     }
 
     public static final void main(String[] argv) {
@@ -286,8 +287,8 @@
         if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
     }
 
-    protected static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
-            throws Zygote.MethodAndArgsCaller {
+    protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
+            ClassLoader classLoader) {
         // If the application calls System.exit(), terminate the process
         // immediately without running any shutdown hooks.  It is not possible to
         // shutdown an Android application gracefully.  Among other things, the
@@ -300,20 +301,13 @@
         VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
         VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
 
-        final Arguments args;
-        try {
-            args = new Arguments(argv);
-        } catch (IllegalArgumentException ex) {
-            Slog.e(TAG, ex.getMessage());
-            // let the process exit
-            return;
-        }
+        final Arguments args = new Arguments(argv);
 
         // The end of of the RuntimeInit event (see #zygoteInit).
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
         // Remaining arguments are passed to the start class's static main
-        invokeStaticMain(args.startClass, args.startArgs, classLoader);
+        return findStaticMain(args.startClass, args.startArgs, classLoader);
     }
 
     /**
@@ -422,4 +416,37 @@
             System.arraycopy(args, curArg, startArgs, 0, startArgs.length);
         }
     }
+
+    /**
+     * Helper class which holds a method and arguments and can call them. This is used as part of
+     * a trampoline to get rid of the initial process setup stack frames.
+     */
+    static class MethodAndArgsCaller implements Runnable {
+        /** method to call */
+        private final Method mMethod;
+
+        /** argument array */
+        private final String[] mArgs;
+
+        public MethodAndArgsCaller(Method method, String[] args) {
+            mMethod = method;
+            mArgs = args;
+        }
+
+        public void run() {
+            try {
+                mMethod.invoke(null, new Object[] { mArgs });
+            } catch (IllegalAccessException ex) {
+                throw new RuntimeException(ex);
+            } catch (InvocationTargetException ex) {
+                Throwable cause = ex.getCause();
+                if (cause instanceof RuntimeException) {
+                    throw (RuntimeException) cause;
+                } else if (cause instanceof Error) {
+                    throw (Error) cause;
+                }
+                throw new RuntimeException(ex);
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index e28079f..7f46a0c 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -26,6 +26,7 @@
 import android.webkit.WebViewFactory;
 import android.webkit.WebViewFactoryProvider;
 
+import java.io.DataOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
@@ -68,8 +69,7 @@
         }
 
         @Override
-        protected boolean handlePreloadPackage(String packagePath, String libsPath,
-                                               String cacheKey) {
+        protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
             Log.i(TAG, "Beginning package preload");
             // Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
             // our children will reuse the same classloader instead of creating their own.
@@ -87,19 +87,28 @@
             // Once we have the classloader, look up the WebViewFactoryProvider implementation and
             // call preloadInZygote() on it to give it the opportunity to preload the native library
             // and perform any other initialisation work that should be shared among the children.
+            boolean preloadSucceeded = false;
             try {
                 Class<WebViewFactoryProvider> providerClass =
                         WebViewFactory.getWebViewProviderClass(loader);
                 Object result = providerClass.getMethod("preloadInZygote").invoke(null);
-                if (!((Boolean)result).booleanValue()) {
+                preloadSucceeded = ((Boolean) result).booleanValue();
+                if (!preloadSucceeded) {
                     Log.e(TAG, "preloadInZygote returned false");
                 }
             } catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
                      IllegalAccessException | InvocationTargetException e) {
                 Log.e(TAG, "Exception while preloading package", e);
             }
+
+            try {
+                DataOutputStream socketOut = getSocketOutputStream();
+                socketOut.writeInt(preloadSucceeded ? 1 : 0);
+            } catch (IOException ioe) {
+                throw new IllegalStateException("Error writing to command socket", ioe);
+            }
+
             Log.i(TAG, "Package preload done");
-            return false;
         }
     }
 
@@ -113,16 +122,23 @@
             throw new RuntimeException("Failed to setpgid(0,0)", ex);
         }
 
+        final Runnable caller;
         try {
             sServer.registerServerSocket("webview_zygote");
-            sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
-            sServer.closeServerSocket();
-        } catch (Zygote.MethodAndArgsCaller caller) {
-            caller.run();
+            // The select loop returns early in the child process after a fork and
+            // loops forever in the zygote.
+            caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
         } catch (RuntimeException e) {
             Log.e(TAG, "Fatal exception:", e);
+            throw e;
+        } finally {
+            sServer.closeServerSocket();
         }
 
-        System.exit(0);
+        // We're in the child process and have exited the select loop. Proceed to execute the
+        // command.
+        if (caller != null) {
+            caller.run();
+        }
     }
 }
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 608bc9f..89328b2 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -25,7 +25,6 @@
 import android.system.StructCapUserHeader;
 import android.util.BootTimingsTraceLog;
 import android.util.Slog;
-import com.android.internal.os.Zygote.MethodAndArgsCaller;
 import dalvik.system.VMRuntime;
 import java.io.DataOutputStream;
 import java.io.FileDescriptor;
@@ -61,37 +60,35 @@
      * @param args The command-line arguments.
      */
     public static void main(String[] args) {
-        try {
-            // Parse our mandatory arguments.
-            int fdNum = Integer.parseInt(args[0], 10);
-            int targetSdkVersion = Integer.parseInt(args[1], 10);
+        // Parse our mandatory arguments.
+        int fdNum = Integer.parseInt(args[0], 10);
+        int targetSdkVersion = Integer.parseInt(args[1], 10);
 
-            // Tell the Zygote what our actual PID is (since it only knows about the
-            // wrapper that it directly forked).
-            if (fdNum != 0) {
-                try {
-                    FileDescriptor fd = new FileDescriptor();
-                    fd.setInt$(fdNum);
-                    DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
-                    os.writeInt(Process.myPid());
-                    os.close();
-                    IoUtils.closeQuietly(fd);
-                } catch (IOException ex) {
-                    Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
-                }
+        // Tell the Zygote what our actual PID is (since it only knows about the
+        // wrapper that it directly forked).
+        if (fdNum != 0) {
+            try {
+                FileDescriptor fd = new FileDescriptor();
+                fd.setInt$(fdNum);
+                DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
+                os.writeInt(Process.myPid());
+                os.close();
+                IoUtils.closeQuietly(fd);
+            } catch (IOException ex) {
+                Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
             }
-
-            // Mimic system Zygote preloading.
-            ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
-                    Trace.TRACE_TAG_DALVIK));
-
-            // Launch the application.
-            String[] runtimeArgs = new String[args.length - 2];
-            System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
-            WrapperInit.wrapperInit(targetSdkVersion, runtimeArgs);
-        } catch (Zygote.MethodAndArgsCaller caller) {
-            caller.run();
         }
+
+        // Mimic system Zygote preloading.
+        ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
+                Trace.TRACE_TAG_DALVIK));
+
+        // Launch the application.
+        String[] runtimeArgs = new String[args.length - 2];
+        System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
+        Runnable r = wrapperInit(targetSdkVersion, runtimeArgs);
+
+        r.run();
     }
 
     /**
@@ -142,8 +139,7 @@
      * @param targetSdkVersion target SDK version
      * @param argv arg strings
      */
-    private static void wrapperInit(int targetSdkVersion, String[] argv)
-            throws Zygote.MethodAndArgsCaller {
+    private static Runnable wrapperInit(int targetSdkVersion, String[] argv) {
         if (RuntimeInit.DEBUG) {
             Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from wrapper");
         }
@@ -165,7 +161,7 @@
 
         // Perform the same initialization that would happen after the Zygote forks.
         Zygote.nativePreApplicationInit();
-        RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
     }
 
     /**
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 0106b81..a9350db 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -221,39 +221,4 @@
             command.append(" '").append(arg.replace("'", "'\\''")).append("'");
         }
     }
-
-    /**
-     * Helper exception class which holds a method and arguments and
-     * can call them. This is used as part of a trampoline to get rid of
-     * the initial process setup stack frames.
-     */
-    public static class MethodAndArgsCaller extends Exception
-            implements Runnable {
-        /** method to call */
-        private final Method mMethod;
-
-        /** argument array */
-        private final String[] mArgs;
-
-        public MethodAndArgsCaller(Method method, String[] args) {
-            mMethod = method;
-            mArgs = args;
-        }
-
-        public void run() {
-            try {
-                mMethod.invoke(null, new Object[] { mArgs });
-            } catch (IllegalAccessException ex) {
-                throw new RuntimeException(ex);
-            } catch (InvocationTargetException ex) {
-                Throwable cause = ex.getCause();
-                if (cause instanceof RuntimeException) {
-                    throw (RuntimeException) cause;
-                } else if (cause instanceof Error) {
-                    throw (Error) cause;
-                }
-                throw new RuntimeException(ex);
-            }
-        }
-    }
 }
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 2013ac0..33382ed 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -26,7 +26,6 @@
 import android.net.LocalSocket;
 import android.os.FactoryTest;
 import android.os.Process;
-import android.os.SELinux;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.system.ErrnoException;
@@ -36,15 +35,13 @@
 import java.io.BufferedReader;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
+import java.io.EOFException;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import libcore.io.IoUtils;
 
 /**
@@ -80,6 +77,7 @@
     private final BufferedReader mSocketReader;
     private final Credentials peer;
     private final String abiList;
+    private boolean isEof;
 
     /**
      * Constructs instance from connected socket.
@@ -106,6 +104,8 @@
             Log.e(TAG, "Cannot read peer credentials", ex);
             throw ex;
         }
+
+        isEof = false;
     }
 
     /**
@@ -118,21 +118,14 @@
     }
 
     /**
-     * Reads one start command from the command socket. If successful,
-     * a child is forked and a {@link Zygote.MethodAndArgsCaller}
-     * exception is thrown in that child while in the parent process,
-     * the method returns normally. On failure, the child is not
-     * spawned and messages are printed to the log and stderr. Returns
-     * a boolean status value indicating whether an end-of-file on the command
-     * socket has been encountered.
+     * Reads one start command from the command socket. If successful, a child is forked and a
+     * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child
+     * process. {@code null} is always returned in the parent process (the zygote).
      *
-     * @return false if command socket should continue to be read from, or
-     * true if an end-of-file has been encountered.
-     * @throws Zygote.MethodAndArgsCaller trampoline to invoke main()
-     * method in child process
+     * If the client closes the socket, an {@code EOF} condition is set, which callers can test
+     * for by calling {@code ZygoteConnection.isClosedByPeer}.
      */
-    boolean runOnce(ZygoteServer zygoteServer) throws Zygote.MethodAndArgsCaller {
-
+    Runnable processOneCommand(ZygoteServer zygoteServer) {
         String args[];
         Arguments parsedArgs = null;
         FileDescriptor[] descriptors;
@@ -141,130 +134,120 @@
             args = readArgumentList();
             descriptors = mSocket.getAncillaryFileDescriptors();
         } catch (IOException ex) {
-            Log.w(TAG, "IOException on command socket " + ex.getMessage());
-            closeSocket();
-            return true;
+            throw new IllegalStateException("IOException on command socket", ex);
         }
 
+        // readArgumentList returns null only when it has reached EOF with no available
+        // data to read. This will only happen when the remote socket has disconnected.
         if (args == null) {
-            // EOF reached.
-            closeSocket();
-            return true;
-        }
-
-        /** the stderr of the most recent request, if avail */
-        PrintStream newStderr = null;
-
-        if (descriptors != null && descriptors.length >= 3) {
-            newStderr = new PrintStream(
-                    new FileOutputStream(descriptors[2]));
+            isEof = true;
+            return null;
         }
 
         int pid = -1;
         FileDescriptor childPipeFd = null;
         FileDescriptor serverPipeFd = null;
 
-        try {
-            parsedArgs = new Arguments(args);
+        parsedArgs = new Arguments(args);
 
-            if (parsedArgs.abiListQuery) {
-                return handleAbiListQuery();
-            }
+        if (parsedArgs.abiListQuery) {
+            handleAbiListQuery();
+            return null;
+        }
 
-            if (parsedArgs.preloadDefault) {
-                return handlePreload();
-            }
+        if (parsedArgs.preloadDefault) {
+            handlePreload();
+            return null;
+        }
 
-            if (parsedArgs.preloadPackage != null) {
-                return handlePreloadPackage(parsedArgs.preloadPackage,
-                        parsedArgs.preloadPackageLibs, parsedArgs.preloadPackageCacheKey);
-            }
+        if (parsedArgs.preloadPackage != null) {
+            handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
+                    parsedArgs.preloadPackageCacheKey);
+            return null;
+        }
 
-            if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
-                throw new ZygoteSecurityException("Client may not specify capabilities: " +
-                        "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
-                        ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
-            }
+        if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
+            throw new ZygoteSecurityException("Client may not specify capabilities: " +
+                    "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
+                    ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
+        }
 
-            applyUidSecurityPolicy(parsedArgs, peer);
-            applyInvokeWithSecurityPolicy(parsedArgs, peer);
+        applyUidSecurityPolicy(parsedArgs, peer);
+        applyInvokeWithSecurityPolicy(parsedArgs, peer);
 
-            applyDebuggerSystemProperty(parsedArgs);
-            applyInvokeWithSystemProperty(parsedArgs);
+        applyDebuggerSystemProperty(parsedArgs);
+        applyInvokeWithSystemProperty(parsedArgs);
 
-            int[][] rlimits = null;
+        int[][] rlimits = null;
 
-            if (parsedArgs.rlimits != null) {
-                rlimits = parsedArgs.rlimits.toArray(intArray2d);
-            }
+        if (parsedArgs.rlimits != null) {
+            rlimits = parsedArgs.rlimits.toArray(intArray2d);
+        }
 
-            int[] fdsToIgnore = null;
+        int[] fdsToIgnore = null;
 
-            if (parsedArgs.invokeWith != null) {
+        if (parsedArgs.invokeWith != null) {
+            try {
                 FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
                 childPipeFd = pipeFds[1];
                 serverPipeFd = pipeFds[0];
                 Os.fcntlInt(childPipeFd, F_SETFD, 0);
-                fdsToIgnore = new int[] { childPipeFd.getInt$(), serverPipeFd.getInt$() };
+                fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
+            } catch (ErrnoException errnoEx) {
+                throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx);
             }
-
-            /**
-             * In order to avoid leaking descriptors to the Zygote child,
-             * the native code must close the two Zygote socket descriptors
-             * in the child process before it switches from Zygote-root to
-             * the UID and privileges of the application being launched.
-             *
-             * In order to avoid "bad file descriptor" errors when the
-             * two LocalSocket objects are closed, the Posix file
-             * descriptors are released via a dup2() call which closes
-             * the socket and substitutes an open descriptor to /dev/null.
-             */
-
-            int [] fdsToClose = { -1, -1 };
-
-            FileDescriptor fd = mSocket.getFileDescriptor();
-
-            if (fd != null) {
-                fdsToClose[0] = fd.getInt$();
-            }
-
-            fd = zygoteServer.getServerSocketFileDescriptor();
-
-            if (fd != null) {
-                fdsToClose[1] = fd.getInt$();
-            }
-
-            fd = null;
-
-            pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
-                    parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
-                    parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
-                    parsedArgs.appDataDir);
-        } catch (ErrnoException ex) {
-            logAndPrintError(newStderr, "Exception creating pipe", ex);
-        } catch (IllegalArgumentException ex) {
-            logAndPrintError(newStderr, "Invalid zygote arguments", ex);
-        } catch (ZygoteSecurityException ex) {
-            logAndPrintError(newStderr,
-                    "Zygote security policy prevents request: ", ex);
         }
 
+        /**
+         * In order to avoid leaking descriptors to the Zygote child,
+         * the native code must close the two Zygote socket descriptors
+         * in the child process before it switches from Zygote-root to
+         * the UID and privileges of the application being launched.
+         *
+         * In order to avoid "bad file descriptor" errors when the
+         * two LocalSocket objects are closed, the Posix file
+         * descriptors are released via a dup2() call which closes
+         * the socket and substitutes an open descriptor to /dev/null.
+         */
+
+        int [] fdsToClose = { -1, -1 };
+
+        FileDescriptor fd = mSocket.getFileDescriptor();
+
+        if (fd != null) {
+            fdsToClose[0] = fd.getInt$();
+        }
+
+        fd = zygoteServer.getServerSocketFileDescriptor();
+
+        if (fd != null) {
+            fdsToClose[1] = fd.getInt$();
+        }
+
+        fd = null;
+
+        pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
+                parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
+                parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
+                parsedArgs.appDataDir);
+
         try {
             if (pid == 0) {
                 // in child
+                zygoteServer.setForkChild();
+
                 zygoteServer.closeServerSocket();
                 IoUtils.closeQuietly(serverPipeFd);
                 serverPipeFd = null;
-                handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
 
-                // should never get here, the child is expected to either
-                // throw Zygote.MethodAndArgsCaller or exec().
-                return true;
+                return handleChildProc(parsedArgs, descriptors, childPipeFd);
             } else {
-                // in parent...pid of < 0 means failure
+                // In the parent. A pid < 0 indicates a failure and will be handled in
+                // handleParentProc.
                 IoUtils.closeQuietly(childPipeFd);
                 childPipeFd = null;
-                return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
+                handleParentProc(pid, descriptors, serverPipeFd);
+                return null;
             }
         } finally {
             IoUtils.closeQuietly(childPipeFd);
@@ -272,15 +255,13 @@
         }
     }
 
-    private boolean handleAbiListQuery() {
+    private void handleAbiListQuery() {
         try {
             final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII);
             mSocketOutStream.writeInt(abiListBytes.length);
             mSocketOutStream.write(abiListBytes);
-            return false;
         } catch (IOException ioe) {
-            Log.e(TAG, "Error writing to command socket", ioe);
-            return true;
+            throw new IllegalStateException("Error writing to command socket", ioe);
         }
     }
 
@@ -290,7 +271,7 @@
      * if no preload was initiated. The latter implies that the zygote is not configured to load
      * resources lazy or that the zygote has already handled a previous request to handlePreload.
      */
-    private boolean handlePreload() {
+    private void handlePreload() {
         try {
             if (isPreloadComplete()) {
                 mSocketOutStream.writeInt(1);
@@ -298,11 +279,8 @@
                 preload();
                 mSocketOutStream.writeInt(0);
             }
-
-            return false;
         } catch (IOException ioe) {
-            Log.e(TAG, "Error writing to command socket", ioe);
-            return true;
+            throw new IllegalStateException("Error writing to command socket", ioe);
         }
     }
 
@@ -314,7 +292,11 @@
         return ZygoteInit.isPreloadComplete();
     }
 
-    protected boolean handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
+    protected DataOutputStream getSocketOutputStream() {
+        return mSocketOutStream;
+    }
+
+    protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
         throw new RuntimeException("Zyogte does not support package preloading");
     }
 
@@ -330,6 +312,10 @@
         }
     }
 
+    boolean isClosedByPeer() {
+        return isEof;
+    }
+
     /**
      * Handles argument parsing for args related to the zygote spawner.
      *
@@ -777,15 +763,9 @@
      * @param parsedArgs non-null; zygote args
      * @param descriptors null-ok; new file descriptors for stdio if available.
      * @param pipeFd null-ok; pipe for communication back to Zygote.
-     * @param newStderr null-ok; stream to use for stderr until stdio
-     * is reopened.
-     *
-     * @throws Zygote.MethodAndArgsCaller on success to
-     * trampoline to code that invokes static main.
      */
-    private void handleChildProc(Arguments parsedArgs,
-            FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
-            throws Zygote.MethodAndArgsCaller {
+    private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
+            FileDescriptor pipeFd) {
         /**
          * By the time we get here, the native code has closed the two actual Zygote
          * socket connections, and substituted /dev/null in their place.  The LocalSocket
@@ -802,7 +782,6 @@
                 for (FileDescriptor fd: descriptors) {
                     IoUtils.closeQuietly(fd);
                 }
-                newStderr = System.err;
             } catch (ErrnoException ex) {
                 Log.e(TAG, "Error reopening stdio", ex);
             }
@@ -819,9 +798,12 @@
                     parsedArgs.niceName, parsedArgs.targetSdkVersion,
                     VMRuntime.getCurrentInstructionSet(),
                     pipeFd, parsedArgs.remainingArgs);
+
+            // Should not get here.
+            throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
         } else {
-            ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion,
-                    parsedArgs.remainingArgs, null /* classLoader */);
+            return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
+                    null /* classLoader */);
         }
     }
 
@@ -833,13 +815,8 @@
      * @param descriptors null-ok; file descriptors for child's new stdio if
      * specified.
      * @param pipeFd null-ok; pipe for communication with child.
-     * @param parsedArgs non-null; zygote args
-     * @return true for "exit command loop" and false for "continue command
-     * loop"
      */
-    private boolean handleParentProc(int pid,
-            FileDescriptor[] descriptors, FileDescriptor pipeFd, Arguments parsedArgs) {
-
+    private void handleParentProc(int pid, FileDescriptor[] descriptors, FileDescriptor pipeFd) {
         if (pid > 0) {
             setChildPgid(pid);
         }
@@ -888,11 +865,8 @@
             mSocketOutStream.writeInt(pid);
             mSocketOutStream.writeBoolean(usingWrapper);
         } catch (IOException ex) {
-            Log.e(TAG, "Error writing to command socket", ex);
-            return true;
+            throw new IllegalStateException("Error writing to command socket", ex);
         }
-
-        return false;
     }
 
     private void setChildPgid(int pid) {
@@ -908,20 +882,4 @@
                 + "normal if peer is not in our session");
         }
     }
-
-    /**
-     * Logs an error message and prints it to the specified stream, if
-     * provided
-     *
-     * @param newStderr null-ok; a standard error stream
-     * @param message non-null; error message
-     * @param ex null-ok an exception
-     */
-    private static void logAndPrintError (PrintStream newStderr,
-            String message, Throwable ex) {
-        Log.e(TAG, message, ex);
-        if (newStderr != null) {
-            newStderr.println(message + (ex == null ? "" : ex));
-        }
-    }
 }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 385976c..b9022bc 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -24,7 +24,6 @@
 import android.icu.impl.CacheValue;
 import android.icu.text.DecimalFormatSymbols;
 import android.icu.util.ULocale;
-import android.net.LocalServerSocket;
 import android.opengl.EGL14;
 import android.os.Build;
 import android.os.IInstalld;
@@ -446,10 +445,7 @@
     /**
      * Finish remaining work for the newly forked system server process.
      */
-    private static void handleSystemServerProcess(
-            ZygoteConnection.Arguments parsedArgs)
-            throws Zygote.MethodAndArgsCaller {
-
+    private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
         // set umask to 0077 so new files and directories will default to owner-only permissions.
         Os.umask(S_IRWXG | S_IRWXO);
 
@@ -495,6 +491,8 @@
             WrapperInit.execApplication(parsedArgs.invokeWith,
                     parsedArgs.niceName, parsedArgs.targetSdkVersion,
                     VMRuntime.getCurrentInstructionSet(), null, args);
+
+            throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
         } else {
             ClassLoader cl = null;
             if (systemServerClasspath != null) {
@@ -506,7 +504,7 @@
             /*
              * Pass the remaining arguments to SystemServer.
              */
-            ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
+            return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
         }
 
         /* should never reach here */
@@ -588,10 +586,13 @@
     }
 
     /**
-     * Prepare the arguments and fork for the system server process.
+     * Prepare the arguments and forks for the system server process.
+     *
+     * Returns an {@code Runnable} that provides an entrypoint into system_server code in the
+     * child process, and {@code null} in the parent.
      */
-    private static boolean startSystemServer(String abiList, String socketName, ZygoteServer zygoteServer)
-            throws Zygote.MethodAndArgsCaller, RuntimeException {
+    private static Runnable forkSystemServer(String abiList, String socketName,
+            ZygoteServer zygoteServer) {
         long capabilities = posixCapabilitiesAsBits(
             OsConstants.CAP_IPC_LOCK,
             OsConstants.CAP_KILL,
@@ -648,10 +649,10 @@
             }
 
             zygoteServer.closeServerSocket();
-            handleSystemServerProcess(parsedArgs);
+            return handleSystemServerProcess(parsedArgs);
         }
 
-        return true;
+        return null;
     }
 
     /**
@@ -682,6 +683,7 @@
             throw new RuntimeException("Failed to setpgid(0,0)", ex);
         }
 
+        final Runnable caller;
         try {
             // Report Zygote start time to tron unless it is a runtime restart
             if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
@@ -751,19 +753,32 @@
             ZygoteHooks.stopZygoteNoThreadCreation();
 
             if (startSystemServer) {
-                startSystemServer(abiList, socketName, zygoteServer);
+                Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
+
+                // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
+                // child (system_server) process.
+                if (r != null) {
+                    r.run();
+                    return;
+                }
             }
 
             Log.i(TAG, "Accepting command socket connections");
-            zygoteServer.runSelectLoop(abiList);
 
-            zygoteServer.closeServerSocket();
-        } catch (Zygote.MethodAndArgsCaller caller) {
-            caller.run();
+            // The select loop returns early in the child process after a fork and
+            // loops forever in the zygote.
+            caller = zygoteServer.runSelectLoop(abiList);
         } catch (Throwable ex) {
             Log.e(TAG, "System zygote died with exception", ex);
-            zygoteServer.closeServerSocket();
             throw ex;
+        } finally {
+            zygoteServer.closeServerSocket();
+        }
+
+        // We're in the child process and have exited the select loop. Proceed to execute the
+        // command.
+        if (caller != null) {
+            caller.run();
         }
     }
 
@@ -807,8 +822,7 @@
      * @param targetSdkVersion target SDK version
      * @param argv arg strings
      */
-    public static final void zygoteInit(int targetSdkVersion, String[] argv,
-            ClassLoader classLoader) throws Zygote.MethodAndArgsCaller {
+    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
         if (RuntimeInit.DEBUG) {
             Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
         }
@@ -818,7 +832,7 @@
 
         RuntimeInit.commonInit();
         ZygoteInit.nativeZygoteInit();
-        RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
     }
 
     private static final native void nativeZygoteInit();
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index 126d9e7..8baa15a 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -25,6 +25,7 @@
 import android.system.StructPollfd;
 import android.util.Log;
 
+import android.util.Slog;
 import java.io.IOException;
 import java.io.FileDescriptor;
 import java.util.ArrayList;
@@ -45,9 +46,18 @@
 
     private LocalServerSocket mServerSocket;
 
+    /**
+     * Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
+     */
+    private boolean mIsForkChild;
+
     ZygoteServer() {
     }
 
+    void setForkChild() {
+        mIsForkChild = true;
+    }
+
     /**
      * Registers a server socket for zygote command connections
      *
@@ -129,11 +139,8 @@
      * Runs the zygote process's select loop. Accepts new connections as
      * they happen, and reads commands from connections one spawn-request's
      * worth at a time.
-     *
-     * @throws Zygote.MethodAndArgsCaller in a child process when a main()
-     * should be executed.
      */
-    void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller {
+    Runnable runSelectLoop(String abiList) {
         ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
         ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
 
@@ -156,15 +163,62 @@
                 if ((pollFds[i].revents & POLLIN) == 0) {
                     continue;
                 }
+
                 if (i == 0) {
                     ZygoteConnection newPeer = acceptCommandPeer(abiList);
                     peers.add(newPeer);
                     fds.add(newPeer.getFileDesciptor());
                 } else {
-                    boolean done = peers.get(i).runOnce(this);
-                    if (done) {
-                        peers.remove(i);
-                        fds.remove(i);
+                    try {
+                        ZygoteConnection connection = peers.get(i);
+                        final Runnable command = connection.processOneCommand(this);
+
+                        if (mIsForkChild) {
+                            // We're in the child. We should always have a command to run at this
+                            // stage if processOneCommand hasn't called "exec".
+                            if (command == null) {
+                                throw new IllegalStateException("command == null");
+                            }
+
+                            return command;
+                        } else {
+                            // We're in the server - we should never have any commands to run.
+                            if (command != null) {
+                                throw new IllegalStateException("command != null");
+                            }
+
+                            // We don't know whether the remote side of the socket was closed or
+                            // not until we attempt to read from it from processOneCommand. This shows up as
+                            // a regular POLLIN event in our regular processing loop.
+                            if (connection.isClosedByPeer()) {
+                                connection.closeSocket();
+                                peers.remove(i);
+                                fds.remove(i);
+                            }
+                        }
+                    } catch (Exception e) {
+                        if (!mIsForkChild) {
+                            // We're in the server so any exception here is one that has taken place
+                            // pre-fork while processing commands or reading / writing from the
+                            // control socket. Make a loud noise about any such exceptions so that
+                            // we know exactly what failed and why.
+
+                            Slog.e(TAG, "Exception executing zygote command: ", e);
+
+                            // Make sure the socket is closed so that the other end knows immediately
+                            // that something has gone wrong and doesn't time out waiting for a
+                            // response.
+                            ZygoteConnection conn = peers.remove(i);
+                            conn.closeSocket();
+
+                            fds.remove(i);
+                        } else {
+                            // We're in the child so any exception caught here has happened post
+                            // fork and before we execute ActivityThread.main (or any other main()
+                            // method). Log the details of the exception and bring down the process.
+                            Log.e(TAG, "Caught post-fork exception in child process.", e);
+                            throw e;
+                        }
                     }
                 }
             }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7555cab..a3cb350 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -279,6 +279,7 @@
     <protected-broadcast android:name="com.android.nfc.cardemulation.action.CLOSE_TAP_DIALOG" />
     <protected-broadcast android:name="com.android.nfc.handover.action.ALLOW_CONNECT" />
     <protected-broadcast android:name="com.android.nfc.handover.action.DENY_CONNECT" />
+    <protected-broadcast android:name="com.android.nfc.handover.action.TIMEOUT_CONNECT" />
     <protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_ON_DETECTED" />
     <protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED" />
     <protected-broadcast android:name="com.android.nfc_extras.action.AID_SELECTED" />
diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml
index d83ccb2..dc47dcf 100644
--- a/core/res/res/layout-land/time_picker_material.xml
+++ b/core/res/res/layout-land/time_picker_material.xml
@@ -17,7 +17,6 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layoutDirection="ltr"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
 
@@ -34,6 +33,7 @@
             android:layoutDirection="ltr"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
             android:paddingTop="@dimen/timepicker_radial_picker_top_margin"
             android:orientation="horizontal">
 
diff --git a/core/res/res/layout/chooser_row.xml b/core/res/res/layout/chooser_row.xml
index 9baa32c..6c1271d 100644
--- a/core/res/res/layout/chooser_row.xml
+++ b/core/res/res/layout/chooser_row.xml
@@ -18,11 +18,9 @@
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:orientation="horizontal"
-              android:layout_width="match_parent" android:layout_height="wrap_content"
-              android:minHeight="80dp"
+              android:layout_width="match_parent"
+              android:layout_height="100dp"
               android:gravity="start|top"
-              android:paddingTop="8dp"
-              android:paddingBottom="8dp"
               android:paddingStart="@dimen/chooser_grid_padding"
               android:paddingEnd="@dimen/chooser_grid_padding"
               android:weightSum="4">
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index 305c8b0..71c153f 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -18,10 +18,9 @@
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:orientation="vertical"
-              android:layout_width="0dp"
+              android:layout_width="76dp"
               android:layout_height="wrap_content"
-              android:layout_weight="1"
-              android:minWidth="80dp"
+              android:minHeight="100dp"
               android:gravity="center"
               android:paddingTop="8dp"
               android:paddingBottom="8dp"
@@ -49,7 +48,7 @@
     <TextView android:id="@android:id/text1"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
-              android:layout_marginTop="8dp"
+              android:layout_marginTop="4dp"
               android:layout_marginLeft="4dp"
               android:layout_marginRight="4dp"
               android:textAppearance="?attr/textAppearanceSmall"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 55030d5..802f027 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -411,24 +411,45 @@
      -->
     <integer translatable="false" name="config_wifi_wakeup_available">0</integer>
 
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <!-- This list is also modified by code within the framework, including:
+    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering.
+
+         Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
+         [1,7,0] for TYPE_WIFI, TYPE_BLUETOOTH, and TYPE_MOBILE.
+
+         This list is also modified by code within the framework, including:
 
              - TYPE_ETHERNET (9) is prepended to this list, and
-             - the output of TelephonyManager.getTetherApnRequired()
-               determines whether both TYPE_MOBILE (0) and TYPE_HIPRI (5)
-               or TYPE_MOBILE_DUN (4) are appended (if not already present).
+
+             - the return value of TelephonyManager.getTetherApnRequired()
+               determines how the array is further modified:
+
+                   * DUN_REQUIRED
+                     TYPE_MOBILE is removed (if present)
+                     TYPE_MOBILE_HIPRI is removed (if present)
+                     TYPE_MOBILE_DUN is appended (if not already present)
+
+                   * DUN_NOT_REQUIRED
+                     TYPE_MOBILE_DUN is removed (if present)
+                     TYPE_MOBILE is appended (if not already present)
+                     TYPE_MOBILE_HIPRI is appended (if not already present)
+
+                   * DUN_UNSPECIFIED
+                     if any of TYPE_MOBILE{,_DUN,_HIPRI} are present:
+                         change nothing
+                     else:
+                         TYPE_MOBILE is appended
+                         TYPE_MOBILE_HIPRI is appended
 
          For other changes applied to this list, now and in the future, see
          com.android.server.connectivity.tethering.TetheringConfiguration.
+
+         Note also: the order of this is important. The first upstream type
+         for which a satisfying network exists is used.
       -->
     <integer-array translatable="false" name="config_tether_upstream_types">
-        <item>0</item>
         <item>1</item>
-        <item>5</item>
         <item>7</item>
+        <item>0</item>
     </integer-array>
 
     <!-- If the DUN connection for this CDMA device supports more than just DUN -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index fa33d56..9f9c883 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -533,6 +533,8 @@
     <dimen name="content_rect_bottom_clip_allowance">20dp</dimen>
 
     <dimen name="chooser_grid_padding">0dp</dimen>
+    <!-- Spacing around the background change frome service to non-service -->
+    <dimen name="chooser_service_spacing">8dp</dimen>
 
     <item type="dimen" name="aerr_padding_list_top">15dp</item>
     <item type="dimen" name="aerr_padding_list_bottom">8dp</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d9ce7fb..6d6064f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3053,4 +3053,5 @@
   <java-symbol type="string" name="popup_window_default_title" />
   <java-symbol type="bool" name="config_showAreaUpdateInfoSettings" />
   <java-symbol type="layout" name="shutdown_dialog" />
+  <java-symbol type="dimen" name="chooser_service_spacing" />
 </resources>
diff --git a/core/tests/coretests/src/android/database/run_newdb_perf_test.sh b/core/tests/coretests/src/android/database/run_newdb_perf_test.sh
index b2f9827..10a62e6 100755
--- a/core/tests/coretests/src/android/database/run_newdb_perf_test.sh
+++ b/core/tests/coretests/src/android/database/run_newdb_perf_test.sh
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 make -j44 FrameworksCoreTests
-adb install -r ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+adb install -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
 adb logcat -c
 
 echo "Running benchmark 5 times"
diff --git a/core/tests/coretests/src/android/util/SparseLongArrayTest.java b/core/tests/coretests/src/android/util/SparseLongArrayTest.java
new file mode 100644
index 0000000..5a5e893
--- /dev/null
+++ b/core/tests/coretests/src/android/util/SparseLongArrayTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2007 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.support.annotation.NonNull;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+/**
+ * Internal tests for {@link SparseLongArray}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SparseLongArrayTest {
+
+    private static final int TEST_SIZE = 1000;
+
+    private SparseLongArray mSparseLongArray;
+    private int[] mKeys;
+    private long[] mValues;
+    private Random mRandom;
+
+    private static boolean isSame(@NonNull SparseLongArray array1,
+            @NonNull SparseLongArray array2) {
+        if (array1.size() != array2.size()) {
+            return false;
+        }
+        for (int i = 0; i < array1.size(); i++) {
+            if (array1.keyAt(i) != array2.keyAt(i) || array1.valueAt(i) != array2.valueAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void assertRemoved(int startIndex, int endIndex) {
+        for (int i = 0; i < TEST_SIZE; i++) {
+            if (i >= startIndex && i <= endIndex) {
+                assertEquals("Entry not removed", Long.MIN_VALUE,
+                        mSparseLongArray.get(mKeys[i], Long.MIN_VALUE));
+            } else {
+                assertEquals("Untouched entry corrupted", mValues[i],
+                        mSparseLongArray.get(mKeys[i]));
+            }
+        }
+    }
+
+    /**
+     * Generates a sorted array of distinct and random keys
+     *
+     * @param size the number of keys to return in the array. Should be < (2^31)/1000.
+     * @return the array of keys
+     */
+    private int[] generateRandomKeys(int size) {
+        final int[] keys = new int[size];
+        keys[0] = -1 * mRandom.nextInt(size * 500);
+        for (int i = 1; i < size; i++) {
+            keys[i] = keys[i - 1] + 1 + mRandom.nextInt(1000);
+            assertTrue(keys[i] > keys[i - 1]);
+        }
+        return keys;
+    }
+
+    @Before
+    public void setUp() {
+        mSparseLongArray = new SparseLongArray();
+        mRandom = new Random(12345);
+        mKeys = generateRandomKeys(TEST_SIZE);
+        mValues = new long[TEST_SIZE];
+        for (int i = 0; i < TEST_SIZE; i++) {
+            mValues[i] = i + 1;
+            mSparseLongArray.put(mKeys[i], mValues[i]);
+        }
+    }
+
+    @Test
+    public void testRemoveAtRange_removeHead() {
+        mSparseLongArray.removeAtRange(0, 100);
+        assertEquals(TEST_SIZE - 100, mSparseLongArray.size());
+        assertRemoved(0, 99);
+    }
+
+    @Test
+    public void testRemoveAtRange_removeTail() {
+        mSparseLongArray.removeAtRange(TEST_SIZE - 200, 200);
+        assertEquals(TEST_SIZE - 200, mSparseLongArray.size());
+        assertRemoved(TEST_SIZE - 200, TEST_SIZE - 1);
+    }
+
+    @Test
+    public void testRemoveAtRange_removeOverflow() {
+        mSparseLongArray.removeAtRange(TEST_SIZE - 100, 200);
+        assertEquals(TEST_SIZE - 100, mSparseLongArray.size());
+        assertRemoved(TEST_SIZE - 100, TEST_SIZE - 1);
+    }
+
+    @Test
+    public void testRemoveAtRange_removeEverything() {
+        mSparseLongArray.removeAtRange(0, TEST_SIZE);
+        assertEquals(0, mSparseLongArray.size());
+        assertRemoved(0, TEST_SIZE - 1);
+    }
+
+    @Test
+    public void testRemoveAtRange_removeMiddle() {
+        mSparseLongArray.removeAtRange(200, 200);
+        assertEquals(TEST_SIZE - 200, mSparseLongArray.size());
+        assertRemoved(200, 399);
+    }
+
+    @Test
+    public void testRemoveAtRange_removeSingle() {
+        mSparseLongArray.removeAtRange(300, 1);
+        assertEquals(TEST_SIZE - 1, mSparseLongArray.size());
+        assertRemoved(300, 300);
+    }
+
+    @Test
+    public void testRemoveAtRange_compareRemoveAt() {
+        final SparseLongArray sparseLongArray2 = mSparseLongArray.clone();
+        assertTrue(isSame(mSparseLongArray, sparseLongArray2));
+
+        final int startIndex = 101;
+        final int endIndex = 200;
+        mSparseLongArray.removeAtRange(startIndex, endIndex - startIndex + 1);
+        for (int i = endIndex; i >= startIndex; i--) {
+            sparseLongArray2.removeAt(i);
+        }
+        assertEquals(TEST_SIZE - (endIndex - startIndex + 1), mSparseLongArray.size());
+        assertRemoved(startIndex, endIndex);
+        assertTrue(isSame(sparseLongArray2, mSparseLongArray));
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
index c539f78..7a6b8d5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
@@ -280,7 +280,7 @@
 
         // Stop timer
         curr = 1000 * (clocks.realtime = clocks.uptime = 161);
-        bi.noteJobFinishLocked(jobName, UID);
+        bi.noteJobFinishLocked(jobName, UID, 0);
 
         // Move to background
         curr = 1000 * (clocks.realtime = clocks.uptime = 202);
@@ -296,7 +296,7 @@
 
         // Stop timer
         curr = 1000 * (clocks.realtime = clocks.uptime = 409);
-        bi.noteJobFinishLocked(jobName, UID);
+        bi.noteJobFinishLocked(jobName, UID, 0);
 
         // Test
         curr = 1000 * (clocks.realtime = clocks.uptime = 657);
@@ -319,7 +319,7 @@
         final String jobName2 = "second_job";
         bi.noteJobStartLocked(jobName2, UID);
         assertEquals(2, bi.getUidStats().get(UID).getJobStats().size());
-        bi.noteJobFinishLocked(jobName2, UID);
+        bi.noteJobFinishLocked(jobName2, UID, 0);
     }
 
     @SmallTest
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
index f01c33f..e81f678 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
@@ -18,9 +18,12 @@
 package com.android.internal.os;
 
 
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -54,11 +57,16 @@
 @SmallTest
 public class BatteryStatsHelperTest extends TestCase {
     private static final long TIME_FOREGROUND_ACTIVITY_ZERO = 0;
-    private static final long TIME_FOREGROUND_ACTIVITY = 100 * DateUtils.MINUTE_IN_MILLIS;
+    private static final long TIME_FOREGROUND_ACTIVITY = 100 * DateUtils.MINUTE_IN_MILLIS * 1000;
+    private static final long TIME_STATE_FOREGROUND_MS = 10 * DateUtils.MINUTE_IN_MILLIS;
+    private static final long TIME_STATE_FOREGROUND_US = TIME_STATE_FOREGROUND_MS * 1000;
 
     private static final int UID = 123456;
     private static final double BATTERY_SCREEN_USAGE = 300;
     private static final double BATTERY_SYSTEM_USAGE = 600;
+    private static final double BATTERY_WIFI_USAGE = 200;
+    private static final double BATTERY_IDLE_USAGE = 600;
+    private static final double BATTERY_BLUETOOTH_USAGE = 300;
     private static final double BATTERY_OVERACCOUNTED_USAGE = 500;
     private static final double BATTERY_UNACCOUNTED_USAGE = 700;
     private static final double BATTERY_APP_USAGE = 100;
@@ -68,6 +76,12 @@
     @Mock
     private BatteryStats.Uid mUid;
     @Mock
+    private BatterySipper mWifiBatterySipper;
+    @Mock
+    private BatterySipper mBluetoothBatterySipper;
+    @Mock
+    private BatterySipper mIdleBatterySipper;
+    @Mock
     private BatterySipper mNormalBatterySipper;
     @Mock
     private BatterySipper mScreenBatterySipper;
@@ -109,6 +123,15 @@
         mUnaccountedBatterySipper.drainType = BatterySipper.DrainType.UNACCOUNTED;
         mUnaccountedBatterySipper.totalPowerMah = BATTERY_UNACCOUNTED_USAGE;
 
+        mWifiBatterySipper.drainType = BatterySipper.DrainType.WIFI;
+        mWifiBatterySipper.totalPowerMah = BATTERY_WIFI_USAGE;
+
+        mBluetoothBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH;
+        mBluetoothBatterySipper.totalPowerMah = BATTERY_BLUETOOTH_USAGE;
+
+        mIdleBatterySipper.drainType = BatterySipper.DrainType.IDLE;
+        mIdleBatterySipper.totalPowerMah = BATTERY_IDLE_USAGE;
+
         mContext = InstrumentationRegistry.getContext();
         mBatteryStatsHelper = spy(new BatteryStatsHelper(mContext));
         mBatteryStatsHelper.setPackageManager(mPackageManager);
@@ -165,6 +188,9 @@
         sippers.add(mSystemBatterySipper);
         sippers.add(mOvercountedBatterySipper);
         sippers.add(mUnaccountedBatterySipper);
+        sippers.add(mWifiBatterySipper);
+        sippers.add(mBluetoothBatterySipper);
+        sippers.add(mIdleBatterySipper);
         doReturn(true).when(mBatteryStatsHelper).isTypeSystem(mSystemBatterySipper);
         doNothing().when(mBatteryStatsHelper).smearScreenBatterySipper(any(), any());
 
@@ -219,6 +245,19 @@
         assertThat(mBatteryStatsHelper.isTypeService(mNormalBatterySipper)).isTrue();
     }
 
+    @Test
+    public void testGetProcessForegroundTimeMs_largerActivityTime_returnMinTime() {
+        doReturn(TIME_STATE_FOREGROUND_US + 500).when(mBatteryStatsHelper)
+                .getForegroundActivityTotalTimeUs(eq(mUid), anyLong());
+        doReturn(TIME_STATE_FOREGROUND_US).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP),
+                anyLong(), anyInt());
+
+        final long time = mBatteryStatsHelper.getProcessForegroundTimeMs(mUid,
+                BatteryStats.STATS_SINCE_CHARGED);
+
+        assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS);
+    }
+
     private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah,
             int uidCode, boolean isUidNull) {
         final BatterySipper sipper = mock(BatterySipper.class);
@@ -227,8 +266,8 @@
         doReturn(uidCode).when(sipper).getUid();
         if (!isUidNull) {
             final BatteryStats.Uid uid = mock(BatteryStats.Uid.class, RETURNS_DEEP_STUBS);
-            doReturn(activityTime).when(mBatteryStatsHelper).getForegroundActivityTotalTimeMs(
-                    eq(uid), anyLong());
+            doReturn(activityTime).when(mBatteryStatsHelper).getProcessForegroundTimeMs(eq(uid),
+                    anyInt());
             doReturn(uidCode).when(uid).getUid();
             sipper.uidObj = uid;
         }
@@ -236,5 +275,4 @@
         return sipper;
     }
 
-
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java
new file mode 100644
index 0000000..450473d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 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 com.android.internal.os;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.IStopUserCallback;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.ArraySet;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsUserLifecycleTests {
+
+    private static final long POLL_INTERVAL_MS = 500;
+    private static final long USER_REMOVE_TIMEOUT_MS = 5_000;
+    private static final long STOP_USER_TIMEOUT_MS = 10_000;
+    private static final long BATTERYSTATS_POLLING_TIMEOUT_MS = 5_000;
+
+    private static final String CPU_DATA_TAG = "cpu";
+    private static final String CPU_FREQ_DATA_TAG = "ctf";
+
+    private int mTestUserId = UserHandle.USER_NULL;
+    private Context mContext;
+    private UserManager mUm;
+    private IActivityManager mIam;
+
+    @BeforeClass
+    public static void setUpOnce() {
+        assumeTrue(UserManager.getMaxSupportedUsers() > 1);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mUm = UserManager.get(mContext);
+        mIam = ActivityManager.getService();
+        final UserInfo user = mUm.createUser("Test_user_" + System.currentTimeMillis() / 1000, 0);
+        assertNotNull("Unable to create test user", user);
+        mTestUserId = user.id;
+        batteryOnScreenOff();
+    }
+
+    @Test
+    public void testNoCpuDataForRemovedUser() throws Exception {
+        mIam.startUserInBackground(mTestUserId);
+        waitUntilTrue("No uids for started user " + mTestUserId,
+                () -> getNumberOfUidsInBatteryStats() > 0, BATTERYSTATS_POLLING_TIMEOUT_MS);
+
+        CountDownLatch stopUserLatch = new CountDownLatch(1);
+        mIam.stopUser(mTestUserId, true, new IStopUserCallback.Stub() {
+            @Override
+            public void userStopped(int userId) throws RemoteException {
+                stopUserLatch.countDown();
+            }
+
+            @Override
+            public void userStopAborted(int userId) throws RemoteException {
+            }
+        });
+        assertTrue("User " + mTestUserId + " could not be stopped",
+                stopUserLatch.await(STOP_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        mUm.removeUser(mTestUserId);
+        waitUntilTrue("Unable to remove user " + mTestUserId, () -> {
+            for (UserInfo user : mUm.getUsers()) {
+                if (user.id == mTestUserId) {
+                    return false;
+                }
+            }
+            return true;
+        }, USER_REMOVE_TIMEOUT_MS);
+        waitUntilTrue("Uids still found for removed user " + mTestUserId,
+                () -> getNumberOfUidsInBatteryStats() == 0, BATTERYSTATS_POLLING_TIMEOUT_MS);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        batteryOffScreenOn();
+        if (mTestUserId != UserHandle.USER_NULL) {
+            mUm.removeUser(mTestUserId);
+        }
+    }
+
+    private int getNumberOfUidsInBatteryStats() throws Exception {
+        ArraySet<Integer> uids = new ArraySet<>();
+        final String dumpsys = executeShellCommand("dumpsys batterystats --checkin");
+        for (String line : dumpsys.split("\n")) {
+            final String[] parts = line.trim().split(",");
+            if (parts.length < 5 ||
+                    (!parts[3].equals(CPU_DATA_TAG) && !parts[3].equals(CPU_FREQ_DATA_TAG))) {
+                continue;
+            }
+            try {
+                final int uid = Integer.parseInt(parts[1]);
+                if (UserHandle.getUserId(uid) == mTestUserId) {
+                    uids.add(uid);
+                }
+            } catch (NumberFormatException nexc) {
+                // ignore
+            }
+        }
+        return uids.size();
+    }
+
+    protected void batteryOnScreenOff() throws Exception {
+        executeShellCommand("dumpsys battery unplug");
+        executeShellCommand("dumpsys batterystats enable pretend-screen-off");
+    }
+
+    protected void batteryOffScreenOn() throws Exception {
+        executeShellCommand("dumpsys battery reset");
+        executeShellCommand("dumpsys batterystats disable pretend-screen-off");
+    }
+
+    private String executeShellCommand(String cmd) throws Exception {
+        return UiDevice.getInstance(
+                InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
+    }
+
+    private void waitUntilTrue(String message, Condition condition, long timeout) throws Exception {
+        final long deadLine = System.currentTimeMillis() + timeout;
+        while (System.currentTimeMillis() <= deadLine && !condition.isTrue()) {
+            Thread.sleep(POLL_INTERVAL_MS);
+        }
+        assertTrue(message, condition.isTrue());
+    }
+
+    private interface Condition {
+        boolean isTrue() throws Exception;
+    }
+}
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index ae5ae1a..ef2950b 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -31,6 +31,20 @@
 
 include $(BUILD_JAVA_LIBRARY)
 
+# Build the repackaged-legacy-test library
+# ========================================
+# This contains repackaged versions of the classes from legacy-test.
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := repackaged-legacy-test
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
 # Generate the stub source files for legacy.test.stubs
 # ====================================================
 include $(CLEAR_VARS)
diff --git a/legacy-test/jarjar-rules.txt b/legacy-test/jarjar-rules.txt
new file mode 100644
index 0000000..9077e6f
--- /dev/null
+++ b/legacy-test/jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule junit.** repackaged.junit.@1
+rule android.test.** repackaged.android.test.@1
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 303d05f..113a477 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -201,6 +201,8 @@
         "PathParser.cpp",
         "PathTessellator.cpp",
         "PixelBuffer.cpp",
+        "ProfileData.cpp",
+        "ProfileDataContainer.cpp",
         "ProfileRenderer.cpp",
         "Program.cpp",
         "ProgramCache.cpp",
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 028d9f7..9d11828 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -34,14 +34,6 @@
 namespace android {
 namespace uirenderer {
 
-static const char* JANK_TYPE_NAMES[] = {
-        "Missed Vsync",
-        "High input latency",
-        "Slow UI thread",
-        "Slow bitmap uploads",
-        "Slow issue draw commands",
-};
-
 struct Comparison {
     FrameInfoIndex start;
     FrameInfoIndex end;
@@ -68,70 +60,13 @@
  */
 static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas;
 
-// The bucketing algorithm controls so to speak
-// If a frame is <= to this it goes in bucket 0
-static const uint32_t kBucketMinThreshold = 5;
-// If a frame is > this, start counting in increments of 2ms
-static const uint32_t kBucket2msIntervals = 32;
-// If a frame is > this, start counting in increments of 4ms
-static const uint32_t kBucket4msIntervals = 48;
-
 // For testing purposes to try and eliminate test infra overhead we will
 // consider any unknown delay of frame start as part of the test infrastructure
 // and filter it out of the frame profile data
 static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync;
 
-// The interval of the slow frame histogram
-static const uint32_t kSlowFrameBucketIntervalMs = 50;
-// The start point of the slow frame bucket in ms
-static const uint32_t kSlowFrameBucketStartMs = 150;
-
-// This will be called every frame, performance sensitive
-// Uses bit twiddling to avoid branching while achieving the packing desired
-static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) {
-    uint32_t index = static_cast<uint32_t>(ns2ms(frameTime));
-    // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result
-    // of negating 1 (twos compliment, yaay) else mask will be 0
-    uint32_t mask = -(index > kBucketMinThreshold);
-    // If index > threshold, this will essentially perform:
-    // amountAboveThreshold = index - threshold;
-    // index = threshold + (amountAboveThreshold / 2)
-    // However if index is <= this will do nothing. It will underflow, do
-    // a right shift by 0 (no-op), then overflow back to the original value
-    index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals))
-            + kBucket4msIntervals;
-    index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals))
-            + kBucket2msIntervals;
-    // If index was < minThreshold at the start of all this it's going to
-    // be a pretty garbage value right now. However, mask is 0 so we'll end
-    // up with the desired result of 0.
-    index = (index - kBucketMinThreshold) & mask;
-    return index;
-}
-
-// Only called when dumping stats, less performance sensitive
-int32_t JankTracker::frameTimeForFrameCountIndex(uint32_t index) {
-    index = index + kBucketMinThreshold;
-    if (index > kBucket2msIntervals) {
-        index += (index - kBucket2msIntervals);
-    }
-    if (index > kBucket4msIntervals) {
-        // This works because it was already doubled by the above if
-        // 1 is added to shift slightly more towards the middle of the bucket
-        index += (index - kBucket4msIntervals) + 1;
-    }
-    return index;
-}
-
-int32_t JankTracker::frameTimeForSlowFrameCountIndex(uint32_t index) {
-    return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
-}
-
-JankTracker::JankTracker(const DisplayInfo& displayInfo) {
-    // By default this will use malloc memory. It may be moved later to ashmem
-    // if there is shared space for it and a request comes in to do that.
-    mData = new ProfileData;
-    reset();
+JankTracker::JankTracker(ProfileDataContainer* globalData, const DisplayInfo& displayInfo) {
+    mGlobalData = globalData;
     nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / displayInfo.fps);
 #if USE_HWC2
     nsecs_t sfOffset = frameIntervalNanos - (displayInfo.presentationDeadline - 1_ms);
@@ -151,82 +86,6 @@
     setFrameInterval(frameIntervalNanos);
 }
 
-JankTracker::~JankTracker() {
-    freeData();
-}
-
-void JankTracker::freeData() {
-    if (mIsMapped) {
-        munmap(mData, sizeof(ProfileData));
-    } else {
-        delete mData;
-    }
-    mIsMapped = false;
-    mData = nullptr;
-}
-
-void JankTracker::rotateStorage() {
-    // If we are mapped we want to stop using the ashmem backend and switch to malloc
-    // We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed
-    // If we aren't sitting on top of ashmem then just do a reset() as it's functionally
-    // equivalent do a free, malloc, reset.
-    if (mIsMapped) {
-        freeData();
-        mData = new ProfileData;
-    }
-    reset();
-}
-
-void JankTracker::switchStorageToAshmem(int ashmemfd) {
-    int regionSize = ashmem_get_size_region(ashmemfd);
-    if (regionSize < 0) {
-        int err = errno;
-        ALOGW("Failed to get ashmem region size from fd %d, err %d %s", ashmemfd, err, strerror(err));
-        return;
-    }
-    if (regionSize < static_cast<int>(sizeof(ProfileData))) {
-        ALOGW("Ashmem region is too small! Received %d, required %u",
-                regionSize, static_cast<unsigned int>(sizeof(ProfileData)));
-        return;
-    }
-    ProfileData* newData = reinterpret_cast<ProfileData*>(
-            mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE,
-            MAP_SHARED, ashmemfd, 0));
-    if (newData == MAP_FAILED) {
-        int err = errno;
-        ALOGW("Failed to move profile data to ashmem fd %d, error = %d",
-                ashmemfd, err);
-        return;
-    }
-
-    // The new buffer may have historical data that we want to build on top of
-    // But let's make sure we don't overflow Just In Case
-    uint32_t divider = 0;
-    if (newData->totalFrameCount > (1 << 24)) {
-        divider = 4;
-    }
-    for (size_t i = 0; i < mData->jankTypeCounts.size(); i++) {
-        newData->jankTypeCounts[i] >>= divider;
-        newData->jankTypeCounts[i] += mData->jankTypeCounts[i];
-    }
-    for (size_t i = 0; i < mData->frameCounts.size(); i++) {
-        newData->frameCounts[i] >>= divider;
-        newData->frameCounts[i] += mData->frameCounts[i];
-    }
-    newData->jankFrameCount >>= divider;
-    newData->jankFrameCount += mData->jankFrameCount;
-    newData->totalFrameCount >>= divider;
-    newData->totalFrameCount += mData->totalFrameCount;
-    if (newData->statStartTime > mData->statStartTime
-            || newData->statStartTime == 0) {
-        newData->statStartTime = mData->statStartTime;
-    }
-
-    freeData();
-    mData = newData;
-    mIsMapped = true;
-}
-
 void JankTracker::setFrameInterval(nsecs_t frameInterval) {
     mFrameInterval = frameInterval;
     mThresholds[kMissedVsync] = 1;
@@ -250,8 +109,7 @@
 
 }
 
-void JankTracker::addFrame(const FrameInfo& frame) {
-    mData->totalFrameCount++;
+void JankTracker::finishFrame(const FrameInfo& frame) {
     // Fast-path for jank-free frames
     int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted);
     if (mDequeueTimeForgiveness
@@ -271,11 +129,11 @@
         }
     }
     LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration);
-    uint32_t framebucket = frameCountIndexForFrameTime(totalDuration);
-    LOG_ALWAYS_FATAL_IF(framebucket < 0, "framebucket < 0 (%u)", framebucket);
+    mData->reportFrame(totalDuration);
+    (*mGlobalData)->reportFrame(totalDuration);
+
     // Keep the fast path as fast as possible.
     if (CC_LIKELY(totalDuration < mFrameInterval)) {
-        mData->frameCounts[framebucket]++;
         return;
     }
 
@@ -284,22 +142,14 @@
         return;
     }
 
-    if (framebucket <= mData->frameCounts.size()) {
-        mData->frameCounts[framebucket]++;
-    } else {
-        framebucket = (ns2ms(totalDuration) - kSlowFrameBucketStartMs)
-                / kSlowFrameBucketIntervalMs;
-        framebucket = std::min(framebucket,
-                static_cast<uint32_t>(mData->slowFrameCounts.size() - 1));
-        mData->slowFrameCounts[framebucket]++;
-    }
-
-    mData->jankFrameCount++;
+    mData->reportJank();
+    (*mGlobalData)->reportJank();
 
     for (int i = 0; i < NUM_BUCKETS; i++) {
         int64_t delta = frame.duration(COMPARISONS[i].start, COMPARISONS[i].end);
         if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) {
-            mData->jankTypeCounts[i]++;
+            mData->reportJankType((JankType) i);
+            (*mGlobalData)->reportJankType((JankType) i);
         }
     }
 }
@@ -320,58 +170,39 @@
     if (sFrameStart != FrameInfoIndex::IntendedVsync) {
         dprintf(fd, "\nNote: Data has been filtered!");
     }
-    dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime);
-    dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount);
-    dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount,
-            (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f);
-    dprintf(fd, "\n50th percentile: %ums", findPercentile(data, 50));
-    dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90));
-    dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95));
-    dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99));
-    for (int i = 0; i < NUM_BUCKETS; i++) {
-        dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]);
-    }
-    dprintf(fd, "\nHISTOGRAM:");
-    for (size_t i = 0; i < data->frameCounts.size(); i++) {
-        dprintf(fd, " %ums=%u", frameTimeForFrameCountIndex(i),
-                data->frameCounts[i]);
-    }
-    for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
-        dprintf(fd, " %ums=%u", frameTimeForSlowFrameCountIndex(i),
-                data->slowFrameCounts[i]);
-    }
+    data->dump(fd);
     dprintf(fd, "\n");
 }
 
+void JankTracker::dumpFrames(int fd) {
+    FILE* file = fdopen(fd, "a");
+    fprintf(file, "\n\n---PROFILEDATA---\n");
+    for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
+        fprintf(file, "%s", FrameInfoNames[i].c_str());
+        fprintf(file, ",");
+    }
+    for (size_t i = 0; i < mFrames.size(); i++) {
+        FrameInfo& frame = mFrames[i];
+        if (frame[FrameInfoIndex::SyncStart] == 0) {
+            continue;
+        }
+        fprintf(file, "\n");
+        for (int i = 0; i < static_cast<int>(FrameInfoIndex::NumIndexes); i++) {
+            fprintf(file, "%" PRId64 ",", frame[i]);
+        }
+    }
+    fprintf(file, "\n---PROFILEDATA---\n\n");
+    fflush(file);
+}
+
 void JankTracker::reset() {
-    mData->jankTypeCounts.fill(0);
-    mData->frameCounts.fill(0);
-    mData->slowFrameCounts.fill(0);
-    mData->totalFrameCount = 0;
-    mData->jankFrameCount = 0;
-    mData->statStartTime = systemTime(CLOCK_MONOTONIC);
+    mFrames.clear();
+    mData->reset();
+    (*mGlobalData)->reset();
     sFrameStart = Properties::filterOutTestOverhead
             ? FrameInfoIndex::HandleInputStart
             : FrameInfoIndex::IntendedVsync;
 }
 
-uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) {
-    int pos = percentile * data->totalFrameCount / 100;
-    int remaining = data->totalFrameCount - pos;
-    for (int i = data->slowFrameCounts.size() - 1; i >= 0; i--) {
-        remaining -= data->slowFrameCounts[i];
-        if (remaining <= 0) {
-            return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
-        }
-    }
-    for (int i = data->frameCounts.size() - 1; i >= 0; i--) {
-        remaining -= data->frameCounts[i];
-        if (remaining <= 0) {
-            return frameTimeForFrameCountIndex(i);
-        }
-    }
-    return 0;
-}
-
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 6ff5d89..e56c079 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -17,6 +17,8 @@
 #define JANKTRACKER_H_
 
 #include "FrameInfo.h"
+#include "ProfileData.h"
+#include "ProfileDataContainer.h"
 #include "renderthread/TimeLord.h"
 #include "utils/RingBuffer.h"
 
@@ -29,31 +31,6 @@
 namespace android {
 namespace uirenderer {
 
-enum JankType {
-    kMissedVsync = 0,
-    kHighInputLatency,
-    kSlowUI,
-    kSlowSync,
-    kSlowRT,
-
-    // must be last
-    NUM_BUCKETS,
-};
-
-// Try to keep as small as possible, should match ASHMEM_SIZE in
-// GraphicsStatsService.java
-struct ProfileData {
-    std::array<uint32_t, NUM_BUCKETS> jankTypeCounts;
-    // See comments on kBucket* constants for what this holds
-    std::array<uint32_t, 57> frameCounts;
-    // Holds a histogram of frame times in 50ms increments from 150ms to 5s
-    std::array<uint16_t, 97> slowFrameCounts;
-
-    uint32_t totalFrameCount;
-    uint32_t jankFrameCount;
-    nsecs_t statStartTime;
-};
-
 enum class JankTrackerType {
     // The default, means there's no description set
     Generic,
@@ -72,31 +49,27 @@
 // TODO: Replace DrawProfiler with this
 class JankTracker {
 public:
-    explicit JankTracker(const DisplayInfo& displayInfo);
-    ~JankTracker();
+    explicit JankTracker(ProfileDataContainer* globalData, const DisplayInfo& displayInfo);
 
     void setDescription(JankTrackerType type, const std::string&& name) {
         mDescription.type = type;
         mDescription.name = name;
     }
 
-    void addFrame(const FrameInfo& frame);
+    FrameInfo* startFrame() { return &mFrames.next(); }
+    void finishFrame(const FrameInfo& frame);
 
-    void dump(int fd) { dumpData(fd, &mDescription, mData); }
+    void dumpStats(int fd) { dumpData(fd, &mDescription, mData.get()); }
+    void dumpFrames(int fd);
     void reset();
 
-    void rotateStorage();
-    void switchStorageToAshmem(int ashmemfd);
-
-    uint32_t findPercentile(int p) { return findPercentile(mData, p); }
-    static int32_t frameTimeForFrameCountIndex(uint32_t index);
-    static int32_t frameTimeForSlowFrameCountIndex(uint32_t index);
+    // Exposed for FrameInfoVisualizer
+    // TODO: Figure out a better way to handle this
+    RingBuffer<FrameInfo, 120>& frames() { return mFrames; }
 
 private:
-    void freeData();
     void setFrameInterval(nsecs_t frameIntervalNanos);
 
-    static uint32_t findPercentile(const ProfileData* data, int p);
     static void dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data);
 
     std::array<int64_t, NUM_BUCKETS> mThresholds;
@@ -109,9 +82,12 @@
     // This is only used if we are in pipelined mode and are using HWC2,
     // otherwise it's 0.
     nsecs_t mDequeueTimeForgiveness = 0;
-    ProfileData* mData;
-    bool mIsMapped = false;
+    ProfileDataContainer mData;
+    ProfileDataContainer* mGlobalData;
     ProfileDataDescription mDescription;
+
+    // Ring buffer large enough for 2 seconds worth of frames
+    RingBuffer<FrameInfo, 120> mFrames;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
new file mode 100644
index 0000000..a295c5d
--- /dev/null
+++ b/libs/hwui/ProfileData.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "ProfileData.h"
+
+#include <cinttypes>
+
+namespace android {
+namespace uirenderer {
+
+static const char* JANK_TYPE_NAMES[] = {
+        "Missed Vsync",
+        "High input latency",
+        "Slow UI thread",
+        "Slow bitmap uploads",
+        "Slow issue draw commands",
+};
+
+// The bucketing algorithm controls so to speak
+// If a frame is <= to this it goes in bucket 0
+static const uint32_t kBucketMinThreshold = 5;
+// If a frame is > this, start counting in increments of 2ms
+static const uint32_t kBucket2msIntervals = 32;
+// If a frame is > this, start counting in increments of 4ms
+static const uint32_t kBucket4msIntervals = 48;
+
+// The interval of the slow frame histogram
+static const uint32_t kSlowFrameBucketIntervalMs = 50;
+// The start point of the slow frame bucket in ms
+static const uint32_t kSlowFrameBucketStartMs = 150;
+
+// This will be called every frame, performance sensitive
+// Uses bit twiddling to avoid branching while achieving the packing desired
+static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) {
+    uint32_t index = static_cast<uint32_t>(ns2ms(frameTime));
+    // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result
+    // of negating 1 (twos compliment, yaay) else mask will be 0
+    uint32_t mask = -(index > kBucketMinThreshold);
+    // If index > threshold, this will essentially perform:
+    // amountAboveThreshold = index - threshold;
+    // index = threshold + (amountAboveThreshold / 2)
+    // However if index is <= this will do nothing. It will underflow, do
+    // a right shift by 0 (no-op), then overflow back to the original value
+    index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals))
+            + kBucket4msIntervals;
+    index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals))
+            + kBucket2msIntervals;
+    // If index was < minThreshold at the start of all this it's going to
+    // be a pretty garbage value right now. However, mask is 0 so we'll end
+    // up with the desired result of 0.
+    index = (index - kBucketMinThreshold) & mask;
+    return index;
+}
+
+// Only called when dumping stats, less performance sensitive
+uint32_t ProfileData::frameTimeForFrameCountIndex(uint32_t index) {
+    index = index + kBucketMinThreshold;
+    if (index > kBucket2msIntervals) {
+        index += (index - kBucket2msIntervals);
+    }
+    if (index > kBucket4msIntervals) {
+        // This works because it was already doubled by the above if
+        // 1 is added to shift slightly more towards the middle of the bucket
+        index += (index - kBucket4msIntervals) + 1;
+    }
+    return index;
+}
+
+uint32_t ProfileData::frameTimeForSlowFrameCountIndex(uint32_t index) {
+    return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
+}
+
+void ProfileData::mergeWith(const ProfileData& other) {
+    // Make sure we don't overflow Just In Case
+    uint32_t divider = 0;
+    if (mTotalFrameCount > (1 << 24)) {
+        divider = 4;
+    }
+    for (size_t i = 0; i < other.mJankTypeCounts.size(); i++) {
+        mJankTypeCounts[i] >>= divider;
+        mJankTypeCounts[i] += other.mJankTypeCounts[i];
+    }
+    for (size_t i = 0; i < other.mFrameCounts.size(); i++) {
+        mFrameCounts[i] >>= divider;
+        mFrameCounts[i] += other.mFrameCounts[i];
+    }
+    mJankFrameCount >>= divider;
+    mJankFrameCount += other.mJankFrameCount;
+    mTotalFrameCount >>= divider;
+    mTotalFrameCount += other.mTotalFrameCount;
+    if (mStatStartTime > other.mStatStartTime
+            || mStatStartTime == 0) {
+        mStatStartTime = other.mStatStartTime;
+    }
+}
+
+void ProfileData::dump(int fd) const {
+    dprintf(fd, "\nStats since: %" PRIu64 "ns", mStatStartTime);
+    dprintf(fd, "\nTotal frames rendered: %u", mTotalFrameCount);
+    dprintf(fd, "\nJanky frames: %u (%.2f%%)", mJankFrameCount,
+            (float) mJankFrameCount / (float) mTotalFrameCount * 100.0f);
+    dprintf(fd, "\n50th percentile: %ums", findPercentile(50));
+    dprintf(fd, "\n90th percentile: %ums", findPercentile(90));
+    dprintf(fd, "\n95th percentile: %ums", findPercentile(95));
+    dprintf(fd, "\n99th percentile: %ums", findPercentile(99));
+    for (int i = 0; i < NUM_BUCKETS; i++) {
+        dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], mJankTypeCounts[i]);
+    }
+    dprintf(fd, "\nHISTOGRAM:");
+    histogramForEach([fd](HistogramEntry entry) {
+        dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount);
+    });
+}
+
+uint32_t ProfileData::findPercentile(int percentile) const {
+    int pos = percentile * mTotalFrameCount / 100;
+    int remaining = mTotalFrameCount - pos;
+    for (int i = mSlowFrameCounts.size() - 1; i >= 0; i--) {
+        remaining -= mSlowFrameCounts[i];
+        if (remaining <= 0) {
+            return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs;
+        }
+    }
+    for (int i = mFrameCounts.size() - 1; i >= 0; i--) {
+        remaining -= mFrameCounts[i];
+        if (remaining <= 0) {
+            return frameTimeForFrameCountIndex(i);
+        }
+    }
+    return 0;
+}
+
+void ProfileData::reset() {
+    mJankTypeCounts.fill(0);
+    mFrameCounts.fill(0);
+    mSlowFrameCounts.fill(0);
+    mTotalFrameCount = 0;
+    mJankFrameCount = 0;
+    mStatStartTime = systemTime(CLOCK_MONOTONIC);
+}
+
+void ProfileData::reportFrame(int64_t duration) {
+    mTotalFrameCount++;
+    uint32_t framebucket = frameCountIndexForFrameTime(duration);
+    if (framebucket <= mFrameCounts.size()) {
+        mFrameCounts[framebucket]++;
+    } else {
+        framebucket = (ns2ms(duration) - kSlowFrameBucketStartMs) / kSlowFrameBucketIntervalMs;
+        framebucket = std::min(framebucket, static_cast<uint32_t>(mSlowFrameCounts.size() - 1));
+        mSlowFrameCounts[framebucket]++;
+    }
+}
+
+void ProfileData::histogramForEach(const std::function<void(HistogramEntry)>& callback) const {
+    for (size_t i = 0; i < mFrameCounts.size(); i++) {
+        callback(HistogramEntry{frameTimeForFrameCountIndex(i), mFrameCounts[i]});
+    }
+    for (size_t i = 0; i < mSlowFrameCounts.size(); i++) {
+        callback(HistogramEntry{frameTimeForSlowFrameCountIndex(i), mSlowFrameCounts[i]});
+    }
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h
new file mode 100644
index 0000000..d53ee29
--- /dev/null
+++ b/libs/hwui/ProfileData.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#include "utils/Macros.h"
+
+#include <utils/Timers.h>
+
+#include <array>
+#include <functional>
+#include <tuple>
+
+namespace android {
+namespace uirenderer {
+
+enum JankType {
+    kMissedVsync = 0,
+    kHighInputLatency,
+    kSlowUI,
+    kSlowSync,
+    kSlowRT,
+
+    // must be last
+    NUM_BUCKETS,
+};
+
+// For testing
+class MockProfileData;
+
+// Try to keep as small as possible, should match ASHMEM_SIZE in
+// GraphicsStatsService.java
+class ProfileData {
+    PREVENT_COPY_AND_ASSIGN(ProfileData);
+
+public:
+    ProfileData() { reset(); }
+
+    void reset();
+    void mergeWith(const ProfileData& other);
+    void dump(int fd) const;
+    uint32_t findPercentile(int percentile) const;
+
+    void reportFrame(int64_t duration);
+    void reportJank() { mJankFrameCount++; }
+    void reportJankType(JankType type) { mJankTypeCounts[static_cast<int>(type)]++; }
+
+    uint32_t totalFrameCount() const { return mTotalFrameCount; }
+    uint32_t jankFrameCount() const { return mJankFrameCount; }
+    nsecs_t statsStartTime() const { return mStatStartTime; }
+    uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }
+
+    struct HistogramEntry {
+        uint32_t renderTimeMs;
+        uint32_t frameCount;
+    };
+    void histogramForEach(const std::function<void(HistogramEntry)>& callback) const;
+
+    constexpr static int HistogramSize() {
+        return std::tuple_size<decltype(ProfileData::mFrameCounts)>::value
+                + std::tuple_size<decltype(ProfileData::mSlowFrameCounts)>::value;
+    }
+
+    // Visible for testing
+    static uint32_t frameTimeForFrameCountIndex(uint32_t index);
+    static uint32_t frameTimeForSlowFrameCountIndex(uint32_t index);
+
+private:
+    // Open our guts up to unit tests
+    friend class MockProfileData;
+
+    std::array <uint32_t, NUM_BUCKETS> mJankTypeCounts;
+    // See comments on kBucket* constants for what this holds
+    std::array<uint32_t, 57> mFrameCounts;
+    // Holds a histogram of frame times in 50ms increments from 150ms to 5s
+    std::array<uint16_t, 97> mSlowFrameCounts;
+
+    uint32_t mTotalFrameCount;
+    uint32_t mJankFrameCount;
+    nsecs_t mStatStartTime;
+};
+
+// For testing
+class MockProfileData : public ProfileData {
+public:
+    std::array<uint32_t, NUM_BUCKETS>& editJankTypeCounts() { return mJankTypeCounts; }
+    std::array<uint32_t, 57>& editFrameCounts() { return mFrameCounts; }
+    std::array<uint16_t, 97>& editSlowFrameCounts() { return mSlowFrameCounts; }
+    uint32_t& editTotalFrameCount() { return mTotalFrameCount; }
+    uint32_t& editJankFrameCount() { return mJankFrameCount; }
+    nsecs_t& editStatStartTime() { return mStatStartTime; }
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
diff --git a/libs/hwui/ProfileDataContainer.cpp b/libs/hwui/ProfileDataContainer.cpp
new file mode 100644
index 0000000..cbf3eb3
--- /dev/null
+++ b/libs/hwui/ProfileDataContainer.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "ProfileDataContainer.h"
+
+#include <log/log.h>
+#include <cutils/ashmem.h>
+
+#include <sys/mman.h>
+
+namespace android {
+namespace uirenderer {
+
+void ProfileDataContainer::freeData() {
+    if (mIsMapped) {
+        munmap(mData, sizeof(ProfileData));
+    } else {
+        delete mData;
+    }
+    mIsMapped = false;
+    mData = nullptr;
+}
+
+void ProfileDataContainer::rotateStorage() {
+    // If we are mapped we want to stop using the ashmem backend and switch to malloc
+    // We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed
+    // If we aren't sitting on top of ashmem then just do a reset() as it's functionally
+    // equivalent do a free, malloc, reset.
+    if (mIsMapped) {
+        freeData();
+        mData = new ProfileData;
+    }
+    mData->reset();
+}
+
+void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) {
+    int regionSize = ashmem_get_size_region(ashmemfd);
+    if (regionSize < 0) {
+        int err = errno;
+        ALOGW("Failed to get ashmem region size from fd %d, err %d %s", ashmemfd, err, strerror(err));
+        return;
+    }
+    if (regionSize < static_cast<int>(sizeof(ProfileData))) {
+        ALOGW("Ashmem region is too small! Received %d, required %u",
+                regionSize, static_cast<unsigned int>(sizeof(ProfileData)));
+        return;
+    }
+    ProfileData* newData = reinterpret_cast<ProfileData*>(
+            mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE,
+                    MAP_SHARED, ashmemfd, 0));
+    if (newData == MAP_FAILED) {
+        int err = errno;
+        ALOGW("Failed to move profile data to ashmem fd %d, error = %d",
+                ashmemfd, err);
+        return;
+    }
+
+    newData->mergeWith(*mData);
+    freeData();
+    mData = newData;
+    mIsMapped = true;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/ProfileDataContainer.h b/libs/hwui/ProfileDataContainer.h
new file mode 100644
index 0000000..d2de241
--- /dev/null
+++ b/libs/hwui/ProfileDataContainer.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#pragma once
+
+#include "ProfileData.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+class ProfileDataContainer {
+    PREVENT_COPY_AND_ASSIGN(ProfileDataContainer);
+public:
+    explicit ProfileDataContainer() {}
+
+    ~ProfileDataContainer() { freeData(); }
+
+    void rotateStorage();
+    void switchStorageToAshmem(int ashmemfd);
+
+    ProfileData* get() { return mData; }
+    ProfileData* operator->() { return mData; }
+
+private:
+    void freeData();
+
+    // By default this will use malloc memory. It may be moved later to ashmem
+    // if there is shared space for it and a request comes in to do that.
+    ProfileData* mData = new ProfileData;
+    bool mIsMapped = false;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 7799248..5d7f594 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -142,8 +142,8 @@
         : mRenderThread(thread)
         , mOpaque(!translucent)
         , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
-        , mJankTracker(thread.mainDisplayInfo())
-        , mProfiler(mFrames)
+        , mJankTracker(&thread.globalProfileData(), thread.mainDisplayInfo())
+        , mProfiler(mJankTracker.frames())
         , mContentDrawBounds(0, 0, 0, 0)
         , mRenderPipeline(std::move(renderPipeline)) {
     rootRenderNode->makeRoot();
@@ -321,7 +321,7 @@
     // If the previous frame was dropped we don't need to hold onto it, so
     // just keep using the previous frame's structure instead
     if (!wasSkipped(mCurrentFrameInfo)) {
-        mCurrentFrameInfo = &mFrames.next();
+        mCurrentFrameInfo = mJankTracker.startFrame();
     }
     mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
     mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued;
@@ -485,8 +485,7 @@
     }
 #endif
 
-    mJankTracker.addFrame(*mCurrentFrameInfo);
-    mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
+    mJankTracker.finishFrame(*mCurrentFrameInfo);
     if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
         mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data());
     }
@@ -625,30 +624,12 @@
 }
 
 void CanvasContext::dumpFrames(int fd) {
-    mJankTracker.dump(fd);
-    FILE* file = fdopen(fd, "a");
-    fprintf(file, "\n\n---PROFILEDATA---\n");
-    for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
-        fprintf(file, "%s", FrameInfoNames[i].c_str());
-        fprintf(file, ",");
-    }
-    for (size_t i = 0; i < mFrames.size(); i++) {
-        FrameInfo& frame = mFrames[i];
-        if (frame[FrameInfoIndex::SyncStart] == 0) {
-            continue;
-        }
-        fprintf(file, "\n");
-        for (int i = 0; i < static_cast<int>(FrameInfoIndex::NumIndexes); i++) {
-            fprintf(file, "%" PRId64 ",", frame[i]);
-        }
-    }
-    fprintf(file, "\n---PROFILEDATA---\n\n");
-    fflush(file);
+    mJankTracker.dumpStats(fd);
+    mJankTracker.dumpFrames(fd);
 }
 
 void CanvasContext::resetFrameStats() {
-    mFrames.clear();
-    mRenderThread.jankTracker().reset();
+    mJankTracker.reset();
 }
 
 void CanvasContext::setName(const std::string&& name) {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index b1f4050..aa6d2f3 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -29,7 +29,6 @@
 #include "RenderNode.h"
 #include "thread/Task.h"
 #include "thread/TaskProcessor.h"
-#include "utils/RingBuffer.h"
 #include "renderthread/RenderTask.h"
 #include "renderthread/RenderThread.h"
 
@@ -253,8 +252,6 @@
     std::vector< sp<RenderNode> > mRenderNodes;
 
     FrameInfo* mCurrentFrameInfo = nullptr;
-    // Ring buffer large enough for 2 seconds worth of frames
-    RingBuffer<FrameInfo, 120> mFrames;
     std::string mName;
     JankTracker mJankTracker;
     FrameInfoVisualizer mProfiler;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 80c2955..370cf52 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -427,12 +427,12 @@
     if (args->dumpFlags & DumpFlags::FrameStats) {
         args->context->dumpFrames(args->fd);
     }
+    if (args->dumpFlags & DumpFlags::JankStats) {
+        args->thread->globalProfileData()->dump(args->fd);
+    }
     if (args->dumpFlags & DumpFlags::Reset) {
         args->context->resetFrameStats();
     }
-    if (args->dumpFlags & DumpFlags::JankStats) {
-        args->thread->jankTracker().dump(args->fd);
-    }
     return nullptr;
 }
 
@@ -458,7 +458,7 @@
 
 CREATE_BRIDGE2(frameTimePercentile, RenderThread* thread, int percentile) {
     return reinterpret_cast<void*>(static_cast<uintptr_t>(
-        args->thread->jankTracker().findPercentile(args->percentile)));
+        args->thread->globalProfileData()->findPercentile(args->percentile)));
 }
 
 uint32_t RenderProxy::frameTimePercentile(int p) {
@@ -483,7 +483,7 @@
 }
 
 CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) {
-    args->thread->jankTracker().switchStorageToAshmem(args->fd);
+    args->thread->globalProfileData().switchStorageToAshmem(args->fd);
     close(args->fd);
     return nullptr;
 }
@@ -497,7 +497,7 @@
 }
 
 CREATE_BRIDGE1(rotateProcessStatsBuffer, RenderThread* thread) {
-    args->thread->jankTracker().rotateStorage();
+    args->thread->globalProfileData().rotateStorage();
     return nullptr;
 }
 
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 13af2c4..72a428f 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -200,13 +200,12 @@
     initializeDisplayEventReceiver();
     mEglManager = new EglManager(*this);
     mRenderState = new RenderState(*this);
-    mJankTracker = new JankTracker(mDisplayInfo);
     mVkManager = new VulkanManager(*this);
     mCacheManager = new CacheManager(mDisplayInfo);
 }
 
 void RenderThread::dumpGraphicsMemory(int fd) {
-    jankTracker().dump(fd);
+    globalProfileData()->dump(fd);
 
     String8 cachesOutput;
     String8 pipeline;
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index d984257..bef47b3 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -97,7 +97,7 @@
     TimeLord& timeLord() { return mTimeLord; }
     RenderState& renderState() const { return *mRenderState; }
     EglManager& eglManager() const { return *mEglManager; }
-    JankTracker& jankTracker() { return *mJankTracker; }
+    ProfileDataContainer& globalProfileData() { return mGlobalProfileData; }
     Readback& readback();
 
     const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; }
@@ -160,7 +160,7 @@
     RenderState* mRenderState;
     EglManager* mEglManager;
 
-    JankTracker* mJankTracker = nullptr;
+    ProfileDataContainer mGlobalProfileData;
     Readback* mReadback = nullptr;
 
     sk_sp<GrContext> mGrContext;
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 87eaa6a..3a77195 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -38,9 +38,7 @@
 constexpr int32_t sHeaderSize = 4;
 static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong");
 
-constexpr int sHistogramSize =
-        std::tuple_size<decltype(ProfileData::frameCounts)>::value +
-        std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value;
+constexpr int sHistogramSize = ProfileData::HistogramSize();
 
 static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto,
         const std::string& package, int versionCode, int64_t startTime, int64_t endTime,
@@ -172,18 +170,18 @@
     proto->set_package_name(package);
     proto->set_version_code(versionCode);
     auto summary = proto->mutable_summary();
-    summary->set_total_frames(summary->total_frames() + data->totalFrameCount);
-    summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount);
+    summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
+    summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
     summary->set_missed_vsync_count(
-            summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]);
+            summary->missed_vsync_count() + data->jankTypeCount(kMissedVsync));
     summary->set_high_input_latency_count(
-            summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]);
+            summary->high_input_latency_count() + data->jankTypeCount(kHighInputLatency));
     summary->set_slow_ui_thread_count(
-            summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]);
+            summary->slow_ui_thread_count() + data->jankTypeCount(kSlowUI));
     summary->set_slow_bitmap_upload_count(
-            summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]);
+            summary->slow_bitmap_upload_count() + data->jankTypeCount(kSlowSync));
     summary->set_slow_draw_count(
-            summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]);
+            summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
 
     bool creatingHistogram = false;
     if (proto->histogram_size() == 0) {
@@ -193,33 +191,20 @@
         LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d",
                 proto->histogram_size(), sHistogramSize);
     }
-    for (size_t i = 0; i < data->frameCounts.size(); i++) {
+    int index = 0;
+    data->histogramForEach([&](ProfileData::HistogramEntry entry) {
         service::GraphicsStatsHistogramBucketProto* bucket;
-        int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i);
         if (creatingHistogram) {
             bucket = proto->add_histogram();
-            bucket->set_render_millis(renderTime);
+            bucket->set_render_millis(entry.renderTimeMs);
         } else {
-            bucket = proto->mutable_histogram(i);
-            LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
-                    "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
+            bucket = proto->mutable_histogram(index);
+            LOG_ALWAYS_FATAL_IF(bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs),
+                    "Frame time mistmatch %d vs. %u", bucket->render_millis(), entry.renderTimeMs);
         }
-        bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]);
-    }
-    for (size_t i = 0; i < data->slowFrameCounts.size(); i++) {
-        service::GraphicsStatsHistogramBucketProto* bucket;
-        int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i);
-        if (creatingHistogram) {
-            bucket = proto->add_histogram();
-            bucket->set_render_millis(renderTime);
-        } else {
-            constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value;
-            bucket = proto->mutable_histogram(offset + i);
-            LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime,
-                    "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime);
-        }
-        bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]);
-    }
+        bucket->set_frame_count(bucket->frame_count() + entry.frameCount);
+        index++;
+    });
 }
 
 static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) {
diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
index f6f7337..fda3a79 100644
--- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -61,17 +61,17 @@
 TEST(GraphicsStats, saveLoad) {
     std::string path = findRootPath() + "/test_saveLoad";
     std::string packageName = "com.test.saveLoad";
-    ProfileData mockData;
-    mockData.jankFrameCount = 20;
-    mockData.totalFrameCount = 100;
-    mockData.statStartTime = 10000;
+    MockProfileData mockData;
+    mockData.editJankFrameCount() = 20;
+    mockData.editTotalFrameCount() = 100;
+    mockData.editStatStartTime() = 10000;
     // Fill with patterned data we can recognize but which won't map to a
     // memset or basic for iteration count
-    for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
-        mockData.frameCounts[i] = ((i % 10) + 1) * 2;
+    for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) {
+        mockData.editFrameCounts()[i] = ((i % 10) + 1) * 2;
     }
-    for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
-        mockData.slowFrameCounts[i] = (i % 5) + 1;
+    for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
+        mockData.editSlowFrameCounts()[i] = (i % 5) + 1;
     }
     GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
     service::GraphicsStatsProto loadedProto;
@@ -87,17 +87,17 @@
     ASSERT_TRUE(loadedProto.has_summary());
     EXPECT_EQ(20, loadedProto.summary().janky_frames());
     EXPECT_EQ(100, loadedProto.summary().total_frames());
-    EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(),
+    EXPECT_EQ(mockData.editFrameCounts().size() + mockData.editSlowFrameCounts().size(),
             (size_t) loadedProto.histogram_size());
     for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) {
         int expectedCount, expectedBucket;
-        if (i < mockData.frameCounts.size()) {
+        if (i < mockData.editFrameCounts().size()) {
             expectedCount = ((i % 10) + 1) * 2;
-            expectedBucket = JankTracker::frameTimeForFrameCountIndex(i);
+            expectedBucket = ProfileData::frameTimeForFrameCountIndex(i);
         } else {
-            int temp = i - mockData.frameCounts.size();
+            int temp = i - mockData.editFrameCounts().size();
             expectedCount = (temp % 5) + 1;
-            expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp);
+            expectedBucket = ProfileData::frameTimeForSlowFrameCountIndex(temp);
         }
         EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count());
         EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis());
@@ -107,26 +107,26 @@
 TEST(GraphicsStats, merge) {
     std::string path = findRootPath() + "/test_merge";
     std::string packageName = "com.test.merge";
-    ProfileData mockData;
-    mockData.jankFrameCount = 20;
-    mockData.totalFrameCount = 100;
-    mockData.statStartTime = 10000;
+    MockProfileData mockData;
+    mockData.editJankFrameCount() = 20;
+    mockData.editTotalFrameCount() = 100;
+    mockData.editStatStartTime() = 10000;
     // Fill with patterned data we can recognize but which won't map to a
     // memset or basic for iteration count
-    for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
-        mockData.frameCounts[i] = ((i % 10) + 1) * 2;
+    for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) {
+        mockData.editFrameCounts()[i] = ((i % 10) + 1) * 2;
     }
-    for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
-        mockData.slowFrameCounts[i] = (i % 5) + 1;
+    for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
+        mockData.editSlowFrameCounts()[i] = (i % 5) + 1;
     }
     GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
-    mockData.jankFrameCount = 50;
-    mockData.totalFrameCount = 500;
-    for (size_t i = 0; i < mockData.frameCounts.size(); i++) {
-        mockData.frameCounts[i] = (i % 5) + 1;
+    mockData.editJankFrameCount() = 50;
+    mockData.editTotalFrameCount() = 500;
+    for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) {
+        mockData.editFrameCounts()[i] = (i % 5) + 1;
     }
-    for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) {
-        mockData.slowFrameCounts[i] = ((i % 10) + 1) * 2;
+    for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
+        mockData.editSlowFrameCounts()[i] = ((i % 10) + 1) * 2;
     }
     GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData);
 
@@ -143,19 +143,19 @@
     ASSERT_TRUE(loadedProto.has_summary());
     EXPECT_EQ(20 + 50, loadedProto.summary().janky_frames());
     EXPECT_EQ(100 + 500, loadedProto.summary().total_frames());
-    EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(),
+    EXPECT_EQ(mockData.editFrameCounts().size() + mockData.editSlowFrameCounts().size(),
             (size_t) loadedProto.histogram_size());
     for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) {
         int expectedCount, expectedBucket;
-        if (i < mockData.frameCounts.size()) {
+        if (i < mockData.editFrameCounts().size()) {
             expectedCount = ((i % 10) + 1) * 2;
             expectedCount += (i % 5) + 1;
-            expectedBucket = JankTracker::frameTimeForFrameCountIndex(i);
+            expectedBucket = ProfileData::frameTimeForFrameCountIndex(i);
         } else {
-            int temp = i - mockData.frameCounts.size();
+            int temp = i - mockData.editFrameCounts().size();
             expectedCount = (temp % 5) + 1;
             expectedCount += ((temp % 10) + 1) * 2;
-            expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp);
+            expectedBucket = ProfileData::frameTimeForSlowFrameCountIndex(temp);
         }
         EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count());
         EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis());
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index a430288..064cc84 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -465,6 +465,8 @@
     <string name="wifi_allow_scan_with_traffic">Always allow Wi\u2011Fi Roam Scans</string>
     <!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] -->
     <string name="mobile_data_always_on">Mobile data always active</string>
+    <!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] -->
+    <string name="tethering_hardware_offload">Tethering hardware acceleration</string>
     <!-- Setting Checkbox title for disabling Bluetooth absolute volume -->
     <string name="bluetooth_disable_absolute_volume">Disable absolute volume</string>
     <!-- Setting Checkbox title for enabling Bluetooth inband ringing -->
@@ -534,6 +536,7 @@
     <!-- Setting Checkbox title whether to enable view attribute inspection -->
     <string name="debug_view_attributes">Enable view attribute inspection</string>
     <string name="mobile_data_always_on_summary">Always keep mobile data active, even when Wi\u2011Fi is active (for fast network switching).</string>
+    <string name="tethering_hardware_offload_summary">Use tethering hardware acceleration if available</string>
     <!-- Title of warning dialog about the implications of enabling USB debugging -->
     <string name="adb_warning_title">Allow USB debugging?</string>
     <!-- Warning text to user about the implications of enabling USB debugging -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index e4e0f7f..c4cbc2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -36,7 +36,8 @@
     }
 
     public boolean applyNewConfig(Resources res) {
-        int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
+        int configChanges = mLastConfiguration.updateFrom(
+                Configuration.generateDelta(mLastConfiguration, res.getConfiguration()));
         boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
         if (densityChanged || (configChanges & (mFlags)) != 0) {
             mLastDensity = res.getDisplayMetrics().densityDpi;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 80b943c..d07da93 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -102,6 +102,7 @@
         // Fine-grained state broadcasts
         addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
         addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
+        addHandler(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, new BatteryLevelChangedHandler());
 
         // Dock event broadcasts
         addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
@@ -376,6 +377,17 @@
             }
         }
     }
+
+    private class BatteryLevelChangedHandler implements Handler {
+        public void onReceive(Context context, Intent intent,
+                BluetoothDevice device) {
+            CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+            if (cachedDevice != null) {
+                cachedDevice.refresh();
+            }
+        }
+    }
+
     boolean readPairedDevices() {
         Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
         if (bondedDevices == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index e279a09..4bb4b40 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -368,6 +368,15 @@
         return mDevice;
     }
 
+    /**
+     * Convenience method that can be mocked - it lets tests avoid having to call getDevice() which
+     * causes problems in tests since BluetoothDevice is final and cannot be mocked.
+     * @return the address of this device
+     */
+    public String getAddress() {
+        return mDevice.getAddress();
+    }
+
     public String getName() {
         return mName;
     }
@@ -410,6 +419,14 @@
         }
     }
 
+    /**
+     * Get battery level from remote device
+     * @return battery level in percentage [0-100], or {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+     */
+    public int getBatteryLevel() {
+        return mDevice.getBatteryLevel();
+    }
+
     void refresh() {
         dispatchAttributesChanged();
     }
@@ -828,7 +845,7 @@
     /**
      * @return resource for string that discribes the connection state of this device.
      */
-    public int getConnectionSummary() {
+    public String getConnectionSummary() {
         boolean profileConnected = false;       // at least one profile is connected
         boolean a2dpNotConnected = false;       // A2DP is preferred but not connected
         boolean hfpNotConnected = false;    // HFP is preferred but not connected
@@ -839,7 +856,7 @@
             switch (connectionStatus) {
                 case BluetoothProfile.STATE_CONNECTING:
                 case BluetoothProfile.STATE_DISCONNECTING:
-                    return Utils.getConnectionStateSummary(connectionStatus);
+                    return mContext.getString(Utils.getConnectionStateSummary(connectionStatus));
 
                 case BluetoothProfile.STATE_CONNECTED:
                     profileConnected = true;
@@ -859,18 +876,54 @@
             }
         }
 
+        String batteryLevelPercentageString = null;
+        // Android framework should only set mBatteryLevel to valid range [0-100] or
+        // BluetoothDevice.BATTERY_LEVEL_UNKNOWN, any other value should be a framework bug.
+        // Thus assume here that if value is not BluetoothDevice.BATTERY_LEVEL_UNKNOWN, it must
+        // be valid
+        final int batteryLevel = getBatteryLevel();
+        if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+            // TODO: name com.android.settingslib.bluetooth.Utils something different
+            batteryLevelPercentageString =
+                    com.android.settingslib.Utils.formatPercentage(batteryLevel);
+        }
+
         if (profileConnected) {
             if (a2dpNotConnected && hfpNotConnected) {
-                return R.string.bluetooth_connected_no_headset_no_a2dp;
+                if (batteryLevelPercentageString != null) {
+                    return mContext.getString(
+                            R.string.bluetooth_connected_no_headset_no_a2dp_battery_level,
+                            batteryLevelPercentageString);
+                } else {
+                    return mContext.getString(R.string.bluetooth_connected_no_headset_no_a2dp);
+                }
+
             } else if (a2dpNotConnected) {
-                return R.string.bluetooth_connected_no_a2dp;
+                if (batteryLevelPercentageString != null) {
+                    return mContext.getString(R.string.bluetooth_connected_no_a2dp_battery_level,
+                            batteryLevelPercentageString);
+                } else {
+                    return mContext.getString(R.string.bluetooth_connected_no_a2dp);
+                }
+
             } else if (hfpNotConnected) {
-                return R.string.bluetooth_connected_no_headset;
+                if (batteryLevelPercentageString != null) {
+                    return mContext.getString(R.string.bluetooth_connected_no_headset_battery_level,
+                            batteryLevelPercentageString);
+                } else {
+                    return mContext.getString(R.string.bluetooth_connected_no_headset);
+                }
             } else {
-                return R.string.bluetooth_connected;
+                if (batteryLevelPercentageString != null) {
+                    return mContext.getString(R.string.bluetooth_connected_battery_level,
+                            batteryLevelPercentageString);
+                } else {
+                    return mContext.getString(R.string.bluetooth_connected);
+                }
             }
         }
 
-        return getBondState() == BluetoothDevice.BOND_BONDING ? R.string.bluetooth_pairing : 0;
+        return getBondState() == BluetoothDevice.BOND_BONDING ?
+                mContext.getString(R.string.bluetooth_pairing) : null;
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 5529866..d45fe1a 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -34,7 +34,7 @@
 /**
  * HeadsetProfile handles Bluetooth HFP and Headset profiles.
  */
-public final class HeadsetProfile implements LocalBluetoothProfile {
+public class HeadsetProfile implements LocalBluetoothProfile {
     private static final String TAG = "HeadsetProfile";
     private static boolean V = true;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index a9e8db5..d1621da 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -31,7 +31,7 @@
 /**
  * HidProfile handles Bluetooth HID profile.
  */
-public final class HidProfile implements LocalBluetoothProfile {
+public class HidProfile implements LocalBluetoothProfile {
     private static final String TAG = "HidProfile";
     private static boolean V = true;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 4a54451..ec41415 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -1053,7 +1053,8 @@
     }
 
     /** Attempt to update the AccessPoint and return true if an update occurred. */
-    public boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
+    public boolean update(
+            @Nullable WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) {
         boolean updated = false;
         final int oldLevel = getLevel();
         if (info != null && isInfoForThisAccessPoint(config, info)) {
@@ -1088,9 +1089,9 @@
         return updated;
     }
 
-    void update(WifiConfiguration config) {
+    void update(@Nullable WifiConfiguration config) {
         mConfig = config;
-        networkId = config.networkId;
+        networkId = config != null ? config.networkId : WifiConfiguration.INVALID_NETWORK_ID;
         if (mAccessPointListener != null) {
             mAccessPointListener.onAccessPointChanged(this);
         }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 5c64cff..c08dd6e 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -656,4 +656,28 @@
         assertThat(ap.update(newConfig, wifiInfo, networkInfo)).isFalse();
         verify(mockListener).onAccessPointChanged(ap);
     }
+
+    @Test
+    public void testUpdateWithNullWifiConfiguration_doesNotThrowNPE() {
+        int networkId = 123;
+        int rssi = -55;
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = networkId;
+        WifiInfo wifiInfo = new WifiInfo();
+        wifiInfo.setNetworkId(networkId);
+        wifiInfo.setRssi(rssi);
+
+        NetworkInfo networkInfo =
+                new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
+        networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTING, "", "");
+
+        AccessPoint ap = new TestAccessPointBuilder(mContext)
+                .setNetworkInfo(networkInfo)
+                .setNetworkId(networkId)
+                .setRssi(rssi)
+                .setWifiInfo(wifiInfo)
+                .build();
+
+        ap.update(null, wifiInfo, networkInfo);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
new file mode 100644
index 0000000..e2ebbeb
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 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 com.android.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settingslib.R;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, resourceDir =
+        "../../res")
+public class CachedBluetoothDeviceTest {
+    @Mock
+    private LocalBluetoothAdapter mAdapter;
+    @Mock
+    private LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    private HeadsetProfile mHfpProfile;
+    @Mock
+    private A2dpProfile mA2dpProfile;
+    @Mock
+    private HidProfile mHidProfile;
+    @Mock
+    private BluetoothDevice mDevice;
+    private CachedBluetoothDevice mCachedDevice;
+    private Context mContext;
+    private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        when(mAdapter.getBluetoothState()).thenReturn(BluetoothAdapter.STATE_ON);
+        when(mHfpProfile.isProfileReady()).thenReturn(true);
+        when(mA2dpProfile.isProfileReady()).thenReturn(true);
+        when(mHidProfile.isProfileReady()).thenReturn(true);
+        mCachedDevice = spy(
+                new CachedBluetoothDevice(mContext, mAdapter, mProfileManager, mDevice));
+        doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel();
+    }
+
+    /**
+     * Test to verify the current test context object works so that we are not checking null
+     * against null
+     */
+    @Test
+    public void testContextMock() {
+        assertThat(mContext.getString(R.string.bluetooth_connected)).isEqualTo("Connected");
+    }
+
+    @Test
+    public void testGetConnectionSummary_testSingleProfileConnectDisconnect() {
+        // Test without battery level
+        // Set HID profile to be connected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+                R.string.bluetooth_connected));
+
+        // Set HID profile to be disconnected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isNull();
+
+        // Test with battery level
+        mBatteryLevel = 10;
+        // Set HID profile to be connected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+                R.string.bluetooth_connected_battery_level,
+                com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+
+        // Set HID profile to be disconnected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isNull();
+
+        // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
+        mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+
+        // Set HID profile to be connected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+                R.string.bluetooth_connected));
+
+        // Set HID profile to be disconnected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isNull();
+    }
+
+    @Test
+    public void testGetConnectionSummary_testMultipleProfileConnectDisconnect() {
+        mBatteryLevel = 10;
+
+        // Set HFP, A2DP and HID profile to be connected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+                R.string.bluetooth_connected_battery_level,
+                com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+
+        // Disconnect HFP only and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+                R.string.bluetooth_connected_no_headset_battery_level,
+                com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+
+        // Disconnect A2DP only and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+                R.string.bluetooth_connected_no_a2dp_battery_level,
+                com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+
+        // Disconnect both HFP and A2DP and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
+                R.string.bluetooth_connected_no_headset_no_a2dp_battery_level,
+                com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
+
+        // Disconnect all profiles and test connection state summary
+        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isNull();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 7fed3e8..377fab5 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -545,6 +545,16 @@
      */
     @VisibleForTesting
     void finishExpanding(boolean forceAbort, float velocity) {
+        finishExpanding(forceAbort, velocity, true /* allowAnimation */);
+    }
+
+    /**
+     * Finish the current expand motion
+     * @param forceAbort whether the expansion should be forcefully aborted and returned to the old
+     *                   state
+     * @param velocity the velocity this was expanded/ collapsed with
+     */
+    private void finishExpanding(boolean forceAbort, float velocity, boolean allowAnimation) {
         if (!mExpanding) return;
 
         if (DEBUG) Log.d(TAG, "scale in finishing on view: " + mResizedView);
@@ -568,7 +578,7 @@
         mCallback.expansionStateChanged(false);
         int naturalHeight = mScaler.getNaturalHeight();
         float targetHeight = nowExpanded ? naturalHeight : mSmallSize;
-        if (targetHeight != currentHeight && mEnabled) {
+        if (targetHeight != currentHeight && mEnabled && allowAnimation) {
             mScaleAnimation.setFloatValues(targetHeight);
             mScaleAnimation.setupStartValues();
             final View scaledView = mResizedView;
@@ -622,10 +632,22 @@
     }
 
     /**
+     * Use this to abort any pending expansions in progress and force that there will be no
+     * animations.
+     */
+    public void cancelImmediately() {
+        cancel(false /* allowAnimation */);
+    }
+
+    /**
      * Use this to abort any pending expansions in progress.
      */
     public void cancel() {
-        finishExpanding(true /* forceAbort */, 0f /* velocity */);
+        cancel(true /* allowAnimation */);
+    }
+
+    private void cancel(boolean allowAnimation) {
+        finishExpanding(true /* forceAbort */, 0f /* velocity */, allowAnimation);
         clearView();
 
         // reset the gesture detector
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 5aaa6c7..180b3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -31,6 +31,7 @@
     void dozeTimeTick();
     boolean isPowerSaveActive();
     boolean isPulsingBlocked();
+    boolean isProvisioned();
 
     void startPendingIntentDismissingKeyguard(PendingIntent intent);
     void abortPulsing();
@@ -40,6 +41,7 @@
 
     void onDoubleTap(float x, float y);
 
+
     interface Callback {
         default void onNotificationHeadsUp() {}
         default void onPowerSaveChanged(boolean active) {}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 9981972..179f5b8 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -172,14 +172,6 @@
         }
     }
 
-    private void onCarMode() {
-        mMachine.requestState(DozeMachine.State.FINISH);
-    }
-
-    private void onPowerSave() {
-        mMachine.requestState(DozeMachine.State.FINISH);
-    }
-
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         switch (newState) {
@@ -215,11 +207,10 @@
     }
 
     private void checkTriggersAtInit() {
-        if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
-            onCarMode();
-        }
-        if (mDozeHost.isPowerSaveActive()) {
-            onPowerSave();
+        if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
+                || mDozeHost.isPowerSaveActive()
+                || !mDozeHost.isProvisioned()) {
+            mMachine.requestState(DozeMachine.State.FINISH);
         }
     }
 
@@ -355,7 +346,7 @@
                 requestPulse(DozeLog.PULSE_REASON_INTENT, false /* performedProxCheck */);
             }
             if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
-                onCarMode();
+                mMachine.requestState(DozeMachine.State.FINISH);
             }
             if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
                 mDozeSensors.onUserSwitched();
@@ -391,7 +382,7 @@
         @Override
         public void onPowerSaveChanged(boolean active) {
             if (active) {
-                onPowerSave();
+                mMachine.requestState(DozeMachine.State.FINISH);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index cdd0ebc..6659650 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -144,22 +144,26 @@
     }
 
     public static void animateGrayScale(int fromColor, int toColor, ImageView iv) {
-        final float fromAlpha = Color.alpha(fromColor);
-        final float toAlpha = Color.alpha(toColor);
-        final float fromChannel = Color.red(fromColor);
-        final float toChannel = Color.red(toColor);
+        if (ValueAnimator.areAnimatorsEnabled()) {
+            final float fromAlpha = Color.alpha(fromColor);
+            final float toAlpha = Color.alpha(toColor);
+            final float fromChannel = Color.red(fromColor);
+            final float toChannel = Color.red(toColor);
 
-        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-        anim.setDuration(QS_ANIM_LENGTH);
+            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+            anim.setDuration(QS_ANIM_LENGTH);
+            anim.addUpdateListener(animation -> {
+                float fraction = animation.getAnimatedFraction();
+                int alpha = (int) (fromAlpha + (toAlpha - fromAlpha) * fraction);
+                int channel = (int) (fromChannel + (toChannel - fromChannel) * fraction);
 
-        anim.addUpdateListener(animation -> {
-            float fraction = animation.getAnimatedFraction();
-            int alpha = (int) (fromAlpha + (toAlpha - fromAlpha) * fraction);
-            int channel = (int) (fromChannel + (toChannel - fromChannel) * fraction);
+                setTint(iv, Color.argb(alpha, channel, channel, channel));
+            });
 
-            setTint(iv, Color.argb(alpha, channel, channel, channel));
-        });
-        anim.start();
+            anim.start();
+        } else {
+            setTint(iv, toColor);
+        }
     }
 
     public static void setTint(ImageView iv, int color) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 36677a4..14afbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -271,7 +272,14 @@
                     int state = mController.getMaxConnectionState(device);
                     if (state == BluetoothProfile.STATE_CONNECTED) {
                         item.icon = R.drawable.ic_qs_bluetooth_connected;
-                        item.line2 = mContext.getString(R.string.quick_settings_connected);
+                        int batteryLevel = device.getBatteryLevel();
+                        if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+                            item.line2 = mContext.getString(
+                                    R.string.quick_settings_connected_battery_level,
+                                    Utils.formatPercentage(batteryLevel));
+                        } else {
+                            item.line2 = mContext.getString(R.string.quick_settings_connected);
+                        }
                         item.canDisconnect = true;
                         items.add(connectedDevices, item);
                         connectedDevices++;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 21dfe8c..968b77f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -400,8 +400,8 @@
             view.draw(c);
         }
         node.end(c);
-        return ThreadedRenderer.createHardwareBitmap(node, bufferWidth, bufferHeight)
-                .createGraphicBufferHandle();
+        Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, bufferWidth, bufferHeight);
+        return hwBitmap.createGraphicBufferHandle();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 674a61c..df1ffda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -168,8 +168,7 @@
                         distance = mTranslationOnDown + distance;
                         distance = Math.max(0, distance);
                     }
-                    setTranslation(distance, false /* isReset */, false /* animateReset */,
-                            false /* force */);
+                    setTranslation(distance, false /* isReset */, false /* animateReset */);
                 }
                 break;
 
@@ -374,12 +373,11 @@
         targetView.finishAnimation(velocity, mAnimationEndRunnable);
     }
 
-    private void setTranslation(float translation, boolean isReset, boolean animateReset,
-            boolean force) {
+    private void setTranslation(float translation, boolean isReset, boolean animateReset) {
         translation = rightSwipePossible() ? translation : Math.max(0, translation);
         translation = leftSwipePossible() ? translation : Math.min(0, translation);
         float absTranslation = Math.abs(translation);
-        if (translation != mTranslation || isReset || force) {
+        if (translation != mTranslation || isReset) {
             KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
             KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
             float alpha = absTranslation / getMinTranslationAmount();
@@ -394,15 +392,15 @@
             boolean slowAnimation = isReset && isBelowFalsingThreshold();
             if (!isReset) {
                 updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
-                        false, false, force, false);
+                        false, false, false, false);
             } else {
                 updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
-                        animateIcons, slowAnimation, force, forceNoCircleAnimation);
+                        animateIcons, slowAnimation, true /* isReset */, forceNoCircleAnimation);
             }
             updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
-                    animateIcons, slowAnimation, force, forceNoCircleAnimation);
+                    animateIcons, slowAnimation, isReset, forceNoCircleAnimation);
             updateIcon(mCenterIcon, 0.0f, fadeOutAlpha * mCenterIcon.getRestingAlpha(),
-                    animateIcons, slowAnimation, force, forceNoCircleAnimation);
+                    animateIcons, slowAnimation, isReset, forceNoCircleAnimation);
 
             mTranslation = translation;
         }
@@ -510,12 +508,8 @@
     }
 
     public void reset(boolean animate) {
-        reset(animate, false /* force */);
-    }
-
-    public void reset(boolean animate, boolean force) {
         cancelAnimation();
-        setTranslation(0.0f, true, animate, force);
+        setTranslation(0.0f, true /* isReset */, animate);
         mMotionCancelled = true;
         if (mSwipingInProgress) {
             mCallback.onSwipingAborted();
@@ -523,10 +517,6 @@
         }
     }
 
-    public void resetImmediately() {
-        reset(false /* animate */, true /* force */);
-    }
-
     public boolean isSwipingInProgress() {
         return mSwipingInProgress;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index a4dfcd8..e9aa6d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -254,6 +254,7 @@
         mFlashlightController = Dependency.get(FlashlightController.class);
         mAccessibilityController = Dependency.get(AccessibilityController.class);
         mAssistManager = Dependency.get(AssistManager.class);
+        mLockIcon.setAccessibilityController(mAccessibilityController);
         updateLeftAffordance();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 9662078..b8ac6b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2561,7 +2561,7 @@
     public void setTouchDisabled(boolean disabled) {
         super.setTouchDisabled(disabled);
         if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
-            mAffordanceHelper.resetImmediately();
+            mAffordanceHelper.reset(false /* animate */);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 12e9c009..b654209 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3770,7 +3770,7 @@
 
     // SystemUIService notifies SystemBars of configuration changes, which then calls down here
     @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
+    public void onConfigChanged(Configuration newConfig) {
         updateResources();
         updateDisplaySize(); // populates mDisplayMetrics
 
@@ -5382,6 +5382,12 @@
         }
 
         @Override
+        public boolean isProvisioned() {
+            return mDeviceProvisionedController.isDeviceProvisioned()
+                    && mDeviceProvisionedController.isCurrentUserSetup();
+        }
+
+        @Override
         public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
             StatusBar.this.startPendingIntentDismissingKeyguard(intent);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 9d197d7..74523e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -3362,15 +3362,29 @@
         if (!mIsExpanded) {
             setOwnScrollY(0);
             mStatusBar.resetUserExpandedStates();
+            clearTemporaryViews();
+            clearUserLockedViews();
+        }
+    }
 
-            // lets make sure nothing is in the overlay / transient anymore
-            clearTemporaryViews(this);
-            for (int i = 0; i < getChildCount(); i++) {
-                ExpandableView child = (ExpandableView) getChildAt(i);
-                if (child instanceof ExpandableNotificationRow) {
-                    ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                    clearTemporaryViews(row.getChildrenContainer());
-                }
+    private void clearUserLockedViews() {
+        for (int i = 0; i < getChildCount(); i++) {
+            ExpandableView child = (ExpandableView) getChildAt(i);
+            if (child instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+                row.setUserLocked(false);
+            }
+        }
+    }
+
+    private void clearTemporaryViews() {
+        // lets make sure nothing is in the overlay / transient anymore
+        clearTemporaryViews(this);
+        for (int i = 0; i < getChildCount(); i++) {
+            ExpandableView child = (ExpandableView) getChildAt(i);
+            if (child instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+                clearTemporaryViews(row.getChildrenContainer());
             }
         }
     }
@@ -3405,6 +3419,7 @@
         if (changed) {
             if (!mIsExpanded) {
                 mGroupManager.collapseAllGroups();
+                mExpandHelper.cancelImmediately();
             }
             updateNotificationAnimationStates();
             updateChronometers();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
index 2345110..8ff9fa9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
@@ -72,6 +72,11 @@
     }
 
     @Override
+    public boolean isProvisioned() {
+        return false;
+    }
+
+    @Override
     public void startPendingIntentDismissingKeyguard(PendingIntent intent) {
         throw new RuntimeException("not implemented");
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 0937ce2..c0de004 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -19,12 +19,13 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
 import com.android.internal.app.NightDisplayController;
 import com.android.systemui.Prefs;
 import com.android.systemui.Prefs.Key;
-
-import android.support.test.filters.SmallTest;
-import android.testing.AndroidTestingRunner;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.qs.QSTileHost;
 
@@ -35,6 +36,7 @@
 import org.mockito.Mockito;
 
 @RunWith(AndroidTestingRunner.class)
+@RunWithLooper
 @SmallTest
 public class AutoTileManagerTest extends SysuiTestCase {
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3566c29..ec1709a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -414,6 +414,7 @@
 import com.android.server.wm.PinnedStackWindowController;
 import com.android.server.wm.WindowManagerService;
 
+import java.text.SimpleDateFormat;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -2648,6 +2649,11 @@
             mService.start();
         }
 
+        @Override
+        public void onCleanupUser(int userId) {
+            mService.mBatteryStatsService.onCleanupUser(userId);
+        }
+
         public ActivityManagerService getService() {
             return mService;
         }
@@ -5502,11 +5508,8 @@
             // NOTE: We should consider creating the file in native code atomically once we've
             // gotten rid of the old scheme of dumping and lot of the code that deals with paths
             // can be removed.
-            try {
-                tracesFile = File.createTempFile("anr_", "", tracesDir);
-                FileUtils.setPermissions(tracesFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
-            } catch (IOException ioe) {
-                Slog.w(TAG, "Unable to create ANR traces file: ", ioe);
+            tracesFile = createAnrDumpFile(tracesDir);
+            if (tracesFile == null) {
                 return null;
             }
 
@@ -5518,6 +5521,31 @@
         return tracesFile;
     }
 
+    @GuardedBy("ActivityManagerService.class")
+    private static SimpleDateFormat sAnrFileDateFormat;
+
+    private static synchronized File createAnrDumpFile(File tracesDir) {
+        if (sAnrFileDateFormat == null) {
+            sAnrFileDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
+        }
+
+        final String formattedDate = sAnrFileDateFormat.format(new Date());
+        final File anrFile = new File(tracesDir, "anr_" + formattedDate);
+
+        try {
+            if (anrFile.createNewFile()) {
+                FileUtils.setPermissions(anrFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
+                return anrFile;
+            } else {
+                Slog.w(TAG, "Unable to create ANR dump file: createNewFile failed");
+            }
+        } catch (IOException ioe) {
+            Slog.w(TAG, "Exception creating ANR dump file:", ioe);
+        }
+
+        return null;
+    }
+
     /**
      * Prune all trace files that are more than a day old.
      *
@@ -17189,12 +17217,12 @@
                             }
                         } catch (IOException e) {
                             if (!isCheckinRequest) {
-                                pw.println("Got IoException!");
+                                pw.println("Got IoException! " + e);
                                 pw.flush();
                             }
                         } catch (RemoteException e) {
                             if (!isCheckinRequest) {
-                                pw.println("Got RemoteException!");
+                                pw.println("Got RemoteException! " + e);
                                 pw.flush();
                             }
                         }
@@ -23740,6 +23768,7 @@
             synchronized (ActivityManagerService.this) {
                 ActivityManagerService.this.onUserStoppedLocked(userId);
             }
+            mBatteryStatsService.onUserRemoved(userId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 77e438a..5b461a1 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2848,8 +2848,9 @@
                     } else {
                         // If a new task is being launched, then mark the existing top activity as
                         // supporting picture-in-picture while pausing
-                        if (focusedTopActivity != null &&
-                                focusedTopActivity.getStack().getStackId() != PINNED_STACK_ID) {
+                        if (focusedTopActivity != null
+                                && focusedTopActivity.getStackId() != PINNED_STACK_ID
+                                && r.getStackId() != ASSISTANT_STACK_ID) {
                             focusedTopActivity.supportsPictureInPictureWhilePausing = true;
                         }
                         transit = TRANSIT_TASK_OPEN;
@@ -4461,7 +4462,8 @@
         }
         // If a new task is moved to the front, then mark the existing top activity as supporting
         // picture-in-picture while paused
-        if (topActivity != null && topActivity.getStack().getStackId() != PINNED_STACK_ID) {
+        if (topActivity != null && topActivity.getStackId() != PINNED_STACK_ID
+                && tr.getStackId() != ASSISTANT_STACK_ID) {
             topActivity.supportsPictureInPictureWhilePausing = true;
         }
 
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index cf322d7..4836bc3 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -34,6 +34,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManagerInternal;
 import android.os.WorkSource;
 import android.os.health.HealthStatsParceler;
 import android.os.health.HealthStatsWriter;
@@ -79,6 +80,7 @@
     private static IBatteryStats sService;
 
     final BatteryStatsImpl mStats;
+    private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;
     private final Context mContext;
     private final BatteryExternalStatsWorker mWorker;
 
@@ -140,7 +142,17 @@
     BatteryStatsService(Context context, File systemDir, Handler handler) {
         // BatteryStatsImpl expects the ActivityManagerService handler, so pass that one through.
         mContext = context;
-        mStats = new BatteryStatsImpl(systemDir, handler, this);
+        mUserManagerUserInfoProvider = new BatteryStatsImpl.UserInfoProvider() {
+            private UserManagerInternal umi;
+            @Override
+            public int[] getUserIds() {
+                if (umi == null) {
+                    umi = LocalServices.getService(UserManagerInternal.class);
+                }
+                return (umi != null) ? umi.getUserIds() : null;
+            }
+        };
+        mStats = new BatteryStatsImpl(systemDir, handler, this, mUserManagerUserInfoProvider);
         mWorker = new BatteryExternalStatsWorker(context, mStats);
         mStats.setExternalStatsSyncLocked(mWorker);
         mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
@@ -242,6 +254,18 @@
         }
     }
 
+    void onCleanupUser(int userId) {
+        synchronized (mStats) {
+            mStats.onCleanupUserLocked(userId);
+        }
+    }
+
+    void onUserRemoved(int userId) {
+        synchronized (mStats) {
+            mStats.onUserRemovedLocked(userId);
+        }
+    }
+
     void addIsolatedUid(int isolatedUid, int appUid) {
         synchronized (mStats) {
             mStats.addIsolatedUidLocked(isolatedUid, appUid);
@@ -369,10 +393,10 @@
         }
     }
 
-    public void noteJobFinish(String name, int uid) {
+    public void noteJobFinish(String name, int uid, int stopReason) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.noteJobFinishLocked(name, uid);
+            mStats.noteJobFinishLocked(name, uid, stopReason);
         }
     }
 
@@ -1260,7 +1284,7 @@
                                 in.unmarshall(raw, 0, raw.length);
                                 in.setDataPosition(0);
                                 BatteryStatsImpl checkinStats = new BatteryStatsImpl(
-                                        null, mStats.mHandler, null);
+                                        null, mStats.mHandler, null, mUserManagerUserInfoProvider);
                                 checkinStats.readSummaryFromParcel(in);
                                 in.recycle();
                                 checkinStats.dumpCheckinLocked(mContext, pw, apps, flags,
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 78487b7..20ec206 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -58,7 +58,12 @@
     }
 
     public void start() {
-        if (isOffloadDisabled() || started()) return;
+        if (started()) return;
+
+        if (isOffloadDisabled()) {
+            mLog.i("tethering offload disabled");
+            return;
+        }
 
         if (!mConfigInitialized) {
             mConfigInitialized = mHwInterface.initOffloadConfig();
diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java
index ba92295..025ff0b 100644
--- a/services/core/java/com/android/server/job/JobPackageTracker.java
+++ b/services/core/java/com/android/server/job/JobPackageTracker.java
@@ -17,11 +17,13 @@
 package com.android.server.job;
 
 import android.app.job.JobInfo;
+import android.app.job.JobParameters;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import com.android.internal.util.RingBufferIndices;
 import com.android.server.job.controllers.JobStatus;
@@ -36,6 +38,9 @@
 
     private static final int EVENT_BUFFER_SIZE = 100;
 
+    public static final int EVENT_CMD_MASK = 0xff;
+    public static final int EVENT_STOP_REASON_SHIFT = 8;
+    public static final int EVENT_STOP_REASON_MASK = 0xff << EVENT_STOP_REASON_SHIFT;
     public static final int EVENT_NULL = 0;
     public static final int EVENT_START_JOB = 1;
     public static final int EVENT_STOP_JOB = 2;
@@ -49,9 +54,9 @@
     private final String[] mEventTags = new String[EVENT_BUFFER_SIZE];
     private final int[] mEventJobIds = new int[EVENT_BUFFER_SIZE];
 
-    public void addEvent(int cmd, int uid, String tag, int jobId) {
+    public void addEvent(int cmd, int uid, String tag, int jobId, int stopReason) {
         int index = mEventIndices.add();
-        mEventCmds[index] = cmd;
+        mEventCmds[index] = cmd | ((stopReason<<EVENT_STOP_REASON_SHIFT) & EVENT_STOP_REASON_MASK);
         mEventTimes[index] = SystemClock.elapsedRealtime();
         mEventUids[index] = uid;
         mEventTags[index] = tag;
@@ -77,6 +82,7 @@
         int pendingNesting;
         int pendingCount;
         boolean hadPending;
+        final SparseIntArray stopReasons = new SparseIntArray();
 
         public long getActiveTime(long now) {
             long time = pastActiveTime;
@@ -179,12 +185,14 @@
             pe.activeNesting++;
         }
 
-        void decActive(int uid, String pkg, long now) {
+        void decActive(int uid, String pkg, long now, int stopReason) {
             PackageEntry pe = getOrCreateEntry(uid, pkg);
             if (pe.activeNesting == 1) {
                 pe.pastActiveTime += now - pe.activeStartTime;
             }
             pe.activeNesting--;
+            int count = pe.stopReasons.get(stopReason, 0);
+            pe.stopReasons.put(stopReason, count+1);
         }
 
         void incActiveTop(int uid, String pkg, long now) {
@@ -196,12 +204,14 @@
             pe.activeTopNesting++;
         }
 
-        void decActiveTop(int uid, String pkg, long now) {
+        void decActiveTop(int uid, String pkg, long now, int stopReason) {
             PackageEntry pe = getOrCreateEntry(uid, pkg);
             if (pe.activeTopNesting == 1) {
                 pe.pastActiveTopTime += now - pe.activeTopStartTime;
             }
             pe.activeTopNesting--;
+            int count = pe.stopReasons.get(stopReason, 0);
+            pe.stopReasons.put(stopReason, count+1);
         }
 
         void finish(DataSet next, long now) {
@@ -261,6 +271,11 @@
                         outPe.pastPendingTime += now - pe.pendingStartTime;
                         outPe.hadPending = true;
                     }
+                    for (int k = pe.stopReasons.size()-1; k >= 0; k--) {
+                        int type = pe.stopReasons.keyAt(k);
+                        outPe.stopReasons.put(type, outPe.stopReasons.get(type, 0)
+                                + pe.stopReasons.valueAt(k));
+                    }
                 }
             }
             if (mMaxTotalActive > out.mMaxTotalActive) {
@@ -312,7 +327,8 @@
                     pw.print(prefix); pw.print("  ");
                     UserHandle.formatUid(pw, uid);
                     pw.print(" / "); pw.print(uidMap.keyAt(j));
-                    pw.print(":");
+                    pw.println(":");
+                    pw.print(prefix); pw.print("   ");
                     printDuration(pw, period, pe.getPendingTime(now), pe.pendingCount, "pending");
                     printDuration(pw, period, pe.getActiveTime(now), pe.activeCount, "active");
                     printDuration(pw, period, pe.getActiveTopTime(now), pe.activeTopCount,
@@ -327,6 +343,18 @@
                         pw.print(" (active-top)");
                     }
                     pw.println();
+                    if (pe.stopReasons.size() > 0) {
+                        pw.print(prefix); pw.print("    ");
+                        for (int k = 0; k < pe.stopReasons.size(); k++) {
+                            if (k > 0) {
+                                pw.print(", ");
+                            }
+                            pw.print(pe.stopReasons.valueAt(k));
+                            pw.print("x ");
+                            pw.print(JobParameters.getReasonName(pe.stopReasons.keyAt(k)));
+                        }
+                        pw.println();
+                    }
                 }
             }
             pw.print(prefix); pw.print("  Max concurrency: ");
@@ -370,19 +398,20 @@
             mCurDataSet.incActive(job.getSourceUid(), job.getSourcePackageName(), now);
         }
         addEvent(job.getJob().isPeriodic() ? EVENT_START_PERIODIC_JOB :  EVENT_START_JOB,
-                job.getSourceUid(), job.getBatteryName(), job.getJobId());
+                job.getSourceUid(), job.getBatteryName(), job.getJobId(), 0);
     }
 
-    public void noteInactive(JobStatus job) {
+    public void noteInactive(JobStatus job, int stopReason) {
         final long now = SystemClock.uptimeMillis();
         if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
-            mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now);
+            mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
+                    stopReason);
         } else {
-            mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now);
+            mCurDataSet.decActive(job.getSourceUid(), job.getSourcePackageName(), now, stopReason);
         }
         rebatchIfNeeded(now);
         addEvent(job.getJob().isPeriodic() ? EVENT_STOP_JOB :  EVENT_STOP_PERIODIC_JOB,
-                job.getSourceUid(), job.getBatteryName(), job.getJobId());
+                job.getSourceUid(), job.getBatteryName(), job.getJobId(), stopReason);
     }
 
     public void noteConcurrency(int totalActive, int fgActive) {
@@ -448,12 +477,12 @@
             if (filterUid != -1 && filterUid != UserHandle.getAppId(uid)) {
                 continue;
             }
-            final int cmd = mEventCmds[index];
+            final int cmd = mEventCmds[index] & EVENT_CMD_MASK;
             if (cmd == EVENT_NULL) {
                 continue;
             }
             final String label;
-            switch (mEventCmds[index]) {
+            switch (cmd) {
                 case EVENT_START_JOB:           label = "  START"; break;
                 case EVENT_STOP_JOB:            label = "   STOP"; break;
                 case EVENT_START_PERIODIC_JOB:  label = "START-P"; break;
@@ -469,7 +498,13 @@
             pw.print("/");
             pw.print(mEventJobIds[index]);
             pw.print(" ");
-            pw.println(mEventTags[index]);
+            pw.print(mEventTags[index]);
+            if (cmd == EVENT_STOP_JOB || cmd == EVENT_STOP_PERIODIC_JOB) {
+                pw.print(" ");
+                pw.print(JobParameters.getReasonName((mEventCmds[index] & EVENT_STOP_REASON_MASK)
+                        >> EVENT_STOP_REASON_SHIFT));
+            }
+            pw.println();
         }
         return true;
     }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 98c65fd..3fea51f 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -2052,7 +2052,15 @@
         synchronized (mLock) {
             boolean foundSome = false;
             for (int i=0; i<mActiveServices.size(); i++) {
-                mActiveServices.get(i).timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId);
+                final JobServiceContext jc = mActiveServices.get(i);
+                final JobStatus js = jc.getRunningJobLocked();
+                if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId)) {
+                    foundSome = true;
+                    pw.print("Timing out: ");
+                    js.printUniqueId(pw);
+                    pw.print(" ");
+                    pw.println(js.getServiceComponent().flattenToShortString());
+                }
             }
             if (!foundSome) {
                 pw.println("No matching executing jobs found.");
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 107475f..a34e251 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -681,6 +681,7 @@
             return;
         }
         try {
+            applyStoppedReasonLocked(reason);
             mVerb = VERB_STOPPING;
             scheduleOpTimeOutLocked();
             service.stopJob(mParams);
@@ -704,10 +705,10 @@
         }
         applyStoppedReasonLocked(reason);
         completedJob = mRunningJob;
-        mJobPackageTracker.noteInactive(completedJob);
+        mJobPackageTracker.noteInactive(completedJob, mParams.getStopReason());
         try {
             mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(),
-                    mRunningJob.getSourceUid());
+                    mRunningJob.getSourceUid(), mParams.getStopReason());
         } catch (RemoteException e) {
             // Whatever.
         }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index c07bd8e..a7cd962 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -67,7 +67,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -589,22 +588,20 @@
     }
 
     private void maybeEnableFactoryTrustAgents(LockPatternUtils utils, int userId) {
+        if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) {
+            return;
+        }
+        PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userId);
         ComponentName defaultAgent = getDefaultFactoryTrustAgent(mContext);
         boolean shouldUseDefaultAgent = defaultAgent != null;
+        ArraySet<ComponentName> discoveredAgents = new ArraySet<>();
 
         if (shouldUseDefaultAgent) {
+            discoveredAgents.add(defaultAgent);
             Log.i(TAG, "Enabling " + defaultAgent + " because it is a default agent.");
-            utils.setEnabledTrustAgents(Collections.singleton(defaultAgent), userId);
         } else { // A default agent is not set; perform regular trust agent discovery
-            if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) {
-                return;
-            }
-            PackageManager pm = mContext.getPackageManager();
-            List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userId);
-
-            ArraySet<ComponentName> discoveredAgents = new ArraySet<>();
-
             for (ResolveInfo resolveInfo : resolveInfos) {
                 ComponentName componentName = getComponentName(resolveInfo);
                 int applicationInfoFlags = resolveInfo.serviceInfo.applicationInfo.flags;
@@ -615,13 +612,13 @@
                 }
                 discoveredAgents.add(componentName);
             }
-
-            List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId);
-            if (previouslyEnabledAgents != null) {
-                discoveredAgents.addAll(previouslyEnabledAgents);
-            }
-            utils.setEnabledTrustAgents(discoveredAgents, userId);
         }
+
+        List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId);
+        if (previouslyEnabledAgents != null) {
+            discoveredAgents.addAll(previouslyEnabledAgents);
+        }
+        utils.setEnabledTrustAgents(discoveredAgents, userId);
         Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, userId);
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9fe7381..05f4626 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1073,7 +1073,7 @@
             }
             if (w.mHasSurface && !rotateSeamlessly) {
                 if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Set mOrientationChanging of " + w);
-                w.mOrientationChanging = true;
+                w.setOrientationChanging(true);
                 mService.mRoot.mOrientationChangeComplete = false;
                 w.mLastFreezeDuration = 0;
             }
@@ -2679,10 +2679,10 @@
         mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
 
         forAllWindows(w -> {
-            if (!w.mOrientationChanging) {
+            if (!w.getOrientationChanging()) {
                 return;
             }
-            w.mOrientationChanging = false;
+            w.setOrientationChanging(false);
             w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
                     - mService.mDisplayFreezeTime);
             Slog.w(TAG_WM, "Force clearing orientation change: " + w);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index edb8853..940ad33 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -18,6 +18,9 @@
 
 import static android.app.ActivityManager.ENABLE_TASK_SNAPSHOTS;
 
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackId;
@@ -29,6 +32,7 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.util.ArraySet;
+import android.util.Slog;
 import android.view.DisplayListCanvas;
 import android.view.RenderNode;
 import android.view.ThreadedRenderer;
@@ -57,6 +61,7 @@
  * To access this class, acquire the global window manager lock.
  */
 class TaskSnapshotController {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
 
     /**
      * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
@@ -154,10 +159,17 @@
                     break;
             }
             if (snapshot != null) {
-                mCache.putSnapshot(task, snapshot);
-                mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
-                if (task.getController() != null) {
-                    task.getController().reportSnapshotChanged(snapshot);
+                final GraphicBuffer buffer = snapshot.getSnapshot();
+                if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
+                    buffer.destroy();
+                    Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
+                            + buffer.getHeight());
+                } else {
+                    mCache.putSnapshot(task, snapshot);
+                    mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+                    if (task.getController() != null) {
+                        task.getController().reportSnapshotChanged(snapshot);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index b628869..f90b3fb 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -291,7 +291,6 @@
                 failed = true;
             }
             if (!writeBuffer()) {
-                writeBuffer();
                 failed = true;
             }
             if (failed) {
@@ -327,11 +326,7 @@
             final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
             final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
             if (bitmap == null) {
-                Slog.e(TAG, "Invalid task snapshot");
-                return false;
-            } else if (bitmap.getWidth() == 0 || bitmap.getHeight() == 0) {
-                Slog.e(TAG, "Invalid task snapshot dimensions " + bitmap.getWidth() + "x"
-                        + bitmap.getHeight());
+                Slog.e(TAG, "Invalid task snapshot hw bitmap");
                 return false;
             }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 20d9286..f9c21b3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5789,7 +5789,7 @@
         // orientation.
         if (!okToDisplay() && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Changing surface while display frozen: " + w);
-            w.mOrientationChanging = true;
+            w.setOrientationChanging(true);
             w.mLastFreezeDuration = 0;
             mRoot.mOrientationChangeComplete = false;
             if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f2cbb6b..17768e6 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -446,7 +446,7 @@
      * Set when the orientation is changing and this window has not yet
      * been updated for the new orientation.
      */
-    boolean mOrientationChanging;
+    private boolean mOrientationChanging;
 
     /**
      * The orientation during the last visible call to relayout. If our
@@ -1189,7 +1189,8 @@
             // then we need to hold off on unfreezing the display until this window has been
             // redrawn; to do that, we need to go through the process of getting informed by the
             // application when it has finished drawing.
-            if (mOrientationChanging || dragResizingChanged || isResizedWhileNotDragResizing()) {
+            if (getOrientationChanging() || dragResizingChanged
+                    || isResizedWhileNotDragResizing()) {
                 if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
                     Slog.v(TAG_WM, "Orientation or resize start waiting for draw"
                             + ", mDrawState=DRAW_PENDING in " + this
@@ -1204,17 +1205,33 @@
                 if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG_WM, "Resizing window " + this);
                 mService.mResizingWindows.add(this);
             }
-        } else if (mOrientationChanging) {
+        } else if (getOrientationChanging()) {
             if (isDrawnLw()) {
                 if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Orientation not waiting for draw in "
                         + this + ", surfaceController " + winAnimator.mSurfaceController);
-                mOrientationChanging = false;
+                setOrientationChanging(false);
                 mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
                         - mService.mDisplayFreezeTime);
             }
         }
     }
 
+    boolean getOrientationChanging() {
+        // In addition to the local state flag, we must also consider the difference in the last
+        // reported configuration vs. the current state. If the client code has not been informed of
+        // the change, logic dependent on having finished processing the orientation, such as
+        // unfreezing, could be improperly triggered.
+        // TODO(b/62846907): Checking against {@link mLastReportedConfiguration} could be flaky as
+        //                   this is not necessarily what the client has processed yet. Find a
+        //                   better indicator consistent with the client.
+        return mOrientationChanging
+                || getConfiguration().orientation != mLastReportedConfiguration.orientation;
+    }
+
+    void setOrientationChanging(boolean changing) {
+        mOrientationChanging = changing;
+    }
+
     DisplayContent getDisplayContent() {
         return mToken.getDisplayContent();
     }
@@ -2688,10 +2705,10 @@
 
         mAppFreezing = false;
 
-        if (mHasSurface && !mOrientationChanging
+        if (mHasSurface && !getOrientationChanging()
                 && mService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
             if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "set mOrientationChanging of " + this);
-            mOrientationChanging = true;
+            setOrientationChanging(true);
             mService.mRoot.mOrientationChangeComplete = false;
         }
         mLastFreezeDuration = 0;
@@ -3110,7 +3127,7 @@
             mWinAnimator.mSurfaceResized = false;
             mReportOrientationChanged = false;
         } catch (RemoteException e) {
-            mOrientationChanging = false;
+            setOrientationChanging(false);
             mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
                     - mService.mDisplayFreezeTime);
             // We are assuming the hosting process is dead or in a zombie state.
@@ -3471,10 +3488,13 @@
                     pw.print(" mDestroying="); pw.print(mDestroying);
                     pw.print(" mRemoved="); pw.println(mRemoved);
         }
-        if (mOrientationChanging || mAppFreezing || mTurnOnScreen
+        if (getOrientationChanging() || mAppFreezing || mTurnOnScreen
                 || mReportOrientationChanged) {
             pw.print(prefix); pw.print("mOrientationChanging=");
                     pw.print(mOrientationChanging);
+                    pw.print(" configOrientationChanging=");
+                    pw.print(mLastReportedConfiguration.orientation
+                            != getConfiguration().orientation);
                     pw.print(" mAppFreezing="); pw.print(mAppFreezing);
                     pw.print(" mTurnOnScreen="); pw.print(mTurnOnScreen);
                     pw.print(" mReportOrientationChanged="); pw.println(mReportOrientationChanged);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 33cb908..df30cfc 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1527,11 +1527,11 @@
 
             // There is no need to wait for an animation change if our window is gone for layout
             // already as we'll never be visible.
-            if (w.mOrientationChanging && w.isGoneForLayoutLw()) {
+            if (w.getOrientationChanging() && w.isGoneForLayoutLw()) {
                 if (DEBUG_ORIENTATION) {
                     Slog.v(TAG, "Orientation change skips hidden " + w);
                 }
-                w.mOrientationChanging = false;
+                w.setOrientationChanging(false);
             }
             return;
         }
@@ -1564,8 +1564,8 @@
             // really hidden (gone for layout), there is no point in still waiting for it.
             // Note that this does introduce a potential glitch if the window becomes unhidden
             // before it has drawn for the new orientation.
-            if (w.mOrientationChanging && w.isGoneForLayoutLw()) {
-                w.mOrientationChanging = false;
+            if (w.getOrientationChanging() && w.isGoneForLayoutLw()) {
+                w.setOrientationChanging(false);
                 if (DEBUG_ORIENTATION) Slog.v(TAG,
                         "Orientation change skips hidden " + w);
             }
@@ -1618,7 +1618,7 @@
                     mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                             WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
                 } else {
-                    w.mOrientationChanging = false;
+                    w.setOrientationChanging(false);
                 }
             }
             if (hasSurface()) {
@@ -1631,14 +1631,14 @@
             displayed = true;
         }
 
-        if (w.mOrientationChanging) {
+        if (w.getOrientationChanging()) {
             if (!w.isDrawnLw()) {
                 mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
                 mAnimator.mLastWindowFreezeSource = w;
                 if (DEBUG_ORIENTATION) Slog.v(TAG,
                         "Orientation continue waiting for draw in " + w);
             } else {
-                w.mOrientationChanging = false;
+                w.setOrientationChanging(false);
                 if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w);
             }
         }
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index e6b567e..dcf5c27 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -83,6 +83,13 @@
      */
     public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF";
 
+    /**
+     * Reason code (returned via {@link #getReason()}), which indicates that the video telephony
+     * call was disconnected because IMS access is blocked.
+     * @hide
+     */
+    public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED";
+
     private int mDisconnectCode;
     private CharSequence mDisconnectLabel;
     private CharSequence mDisconnectDescription;
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
index b3f46c2..231f2c8 100644
--- a/telephony/java/android/telephony/MbmsDownloadManager.java
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -93,19 +93,21 @@
     /**
      * Integer extra indicating the result code of the download. One of
      * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, or {@link #RESULT_CANCELLED}.
+     * TODO: Not systemapi.
      */
     public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT";
 
     /**
      * Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result
      * is for. Must not be null.
-     * TODO: future systemapi (here and and all extras) except the two for the app intent
+     * TODO: Not systemapi.
      */
     public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO";
 
     /**
      * Extra containing the {@link DownloadRequest} for which the download result or file
      * descriptor request is for. Must not be null.
+     * TODO: future systemapi (here and and all extras) except the three for the app intent
      */
     public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
 
@@ -180,6 +182,7 @@
      * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}.
      * Will always be set to a non-null value if {@link #EXTRA_RESULT} is set to
      * {@link #RESULT_SUCCESSFUL}.
+     * TODO: Not systemapi.
      */
     public static final String EXTRA_COMPLETED_FILE_URI =
             "android.telephony.mbms.extra.COMPLETED_FILE_URI";
@@ -554,7 +557,7 @@
 
     private File getDownloadRequestTokenPath(DownloadRequest request) {
         File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
-                request.getFileServiceInfo());
+                request.getFileServiceId());
         String downloadTokenFileName = request.getHash()
                 + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
         return new File(tempFileLocation, downloadTokenFileName);
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 345729d..01e0bbd 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -21,7 +21,14 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Base64;
+import android.util.Log;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
@@ -35,24 +42,52 @@
 public class DownloadRequest implements Parcelable {
     // Version code used to keep token calculation consistent.
     private static final int CURRENT_VERSION = 1;
+    private static final String LOG_TAG = "MbmsDownloadRequest";
+
+    /**
+     * Maximum permissible length for the app's download-completion intent, when serialized via
+     * {@link Intent#toUri(int)}.
+     */
+    public static final int MAX_APP_INTENT_SIZE = 50000;
+
+    /**
+     * Maximum permissible length for the app's destination path, when serialized via
+     * {@link Uri#toString()}.
+     */
+    public static final int MAX_DESTINATION_URI_SIZE = 50000;
 
     /** @hide */
+    private static class OpaqueDataContainer implements Serializable {
+        private final String destinationUri;
+        private final String appIntent;
+        private final int version;
+
+        public OpaqueDataContainer(String destinationUri, String appIntent, int version) {
+            this.destinationUri = destinationUri;
+            this.appIntent = appIntent;
+            this.version = version;
+        }
+    }
+
     public static class Builder {
-        private int id;
-        private FileServiceInfo serviceInfo;
+        private String fileServiceId;
         private Uri source;
         private Uri dest;
         private int subscriptionId;
         private String appIntent;
         private int version = CURRENT_VERSION;
 
-        public Builder setId(int id) {
-            this.id = id;
+        public Builder setServiceInfo(FileServiceInfo serviceInfo) {
+            fileServiceId = serviceInfo.getServiceId();
             return this;
         }
 
-        public Builder setServiceInfo(FileServiceInfo serviceInfo) {
-            this.serviceInfo = serviceInfo;
+        /**
+         * @hide
+         * TODO: systemapi
+         */
+        public Builder setServiceId(String serviceId) {
+            fileServiceId = serviceId;
             return this;
         }
 
@@ -62,6 +97,10 @@
         }
 
         public Builder setDest(Uri dest) {
+            if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) {
+                throw new IllegalArgumentException("Destination uri must not exceed length " +
+                        MAX_DESTINATION_URI_SIZE);
+            }
             this.dest = dest;
             return this;
         }
@@ -73,33 +112,53 @@
 
         public Builder setAppIntent(Intent intent) {
             this.appIntent = intent.toUri(0);
+            if (this.appIntent.length() > MAX_APP_INTENT_SIZE) {
+                throw new IllegalArgumentException("App intent must not exceed length " +
+                        MAX_APP_INTENT_SIZE);
+            }
             return this;
         }
 
-        public Builder setVersion(int version) {
-            this.version = version;
+        /**
+         * For use by middleware only
+         * TODO: systemapi
+         * @hide
+         */
+        public Builder setOpaqueData(byte[] data) {
+            try {
+                ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data));
+                OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject();
+                version = dataContainer.version;
+                appIntent = dataContainer.appIntent;
+                dest = Uri.parse(dataContainer.destinationUri);
+            } catch (IOException e) {
+                // Really should never happen
+                Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
+                throw new IllegalArgumentException(e);
+            } catch (ClassNotFoundException e) {
+                Log.e(LOG_TAG, "Got ClassNotFoundException trying to parse opaque data");
+                throw new IllegalArgumentException(e);
+            }
             return this;
         }
 
         public DownloadRequest build() {
-            return new DownloadRequest(id, serviceInfo, source, dest,
+            return new DownloadRequest(fileServiceId, source, dest,
                     subscriptionId, appIntent, version);
         }
     }
 
-    private final int downloadId;
-    private final FileServiceInfo fileServiceInfo;
+    private final String fileServiceId;
     private final Uri sourceUri;
     private final Uri destinationUri;
     private final int subscriptionId;
     private final String serializedResultIntentForApp;
     private final int version;
 
-    private DownloadRequest(int id, FileServiceInfo serviceInfo,
+    private DownloadRequest(String fileServiceId,
             Uri source, Uri dest,
             int sub, String appIntent, int version) {
-        downloadId = id;
-        fileServiceInfo = serviceInfo;
+        this.fileServiceId = fileServiceId;
         sourceUri = source;
         destinationUri = dest;
         subscriptionId = sub;
@@ -112,8 +171,7 @@
     }
 
     private DownloadRequest(DownloadRequest dr) {
-        downloadId = dr.downloadId;
-        fileServiceInfo = dr.fileServiceInfo;
+        fileServiceId = dr.fileServiceId;
         sourceUri = dr.sourceUri;
         destinationUri = dr.destinationUri;
         subscriptionId = dr.subscriptionId;
@@ -122,8 +180,7 @@
     }
 
     private DownloadRequest(Parcel in) {
-        downloadId = in.readInt();
-        fileServiceInfo = in.readParcelable(getClass().getClassLoader());
+        fileServiceId = in.readString();
         sourceUri = in.readParcelable(getClass().getClassLoader());
         destinationUri = in.readParcelable(getClass().getClassLoader());
         subscriptionId = in.readInt();
@@ -136,8 +193,7 @@
     }
 
     public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(downloadId);
-        out.writeParcelable(fileServiceInfo, flags);
+        out.writeString(fileServiceId);
         out.writeParcelable(sourceUri, flags);
         out.writeParcelable(destinationUri, flags);
         out.writeInt(subscriptionId);
@@ -145,12 +201,8 @@
         out.writeInt(version);
     }
 
-    public int getDownloadId() {
-        return downloadId;
-    }
-
-    public FileServiceInfo getFileServiceInfo() {
-        return fileServiceInfo;
+    public String getFileServiceId() {
+        return fileServiceId;
     }
 
     public Uri getSourceUri() {
@@ -173,6 +225,27 @@
         }
     }
 
+    /**
+     * @hide
+     * TODO: systemapi
+     */
+    public byte[] getOpaqueData() {
+        try {
+            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+            ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
+            OpaqueDataContainer container = new OpaqueDataContainer(
+                    destinationUri.toString(), serializedResultIntentForApp, version);
+            stream.writeObject(container);
+            stream.flush();
+            return byteArrayOutputStream.toByteArray();
+        } catch (IOException e) {
+            // Really should never happen
+            Log.e(LOG_TAG, "Got IOException trying to serialize opaque data");
+            return null;
+        }
+    }
+
+    /** @hide */
     public int getVersion() {
         return version;
     }
@@ -228,10 +301,9 @@
             return false;
         }
         DownloadRequest request = (DownloadRequest) o;
-        return downloadId == request.downloadId &&
-                subscriptionId == request.subscriptionId &&
+        return subscriptionId == request.subscriptionId &&
                 version == request.version &&
-                Objects.equals(fileServiceInfo, request.fileServiceInfo) &&
+                Objects.equals(fileServiceId, request.fileServiceId) &&
                 Objects.equals(sourceUri, request.sourceUri) &&
                 Objects.equals(destinationUri, request.destinationUri) &&
                 Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp);
@@ -239,7 +311,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(downloadId, fileServiceInfo, sourceUri, destinationUri,
+        return Objects.hash(fileServiceId, sourceUri, destinationUri,
                 subscriptionId, serializedResultIntentForApp, version);
     }
 }
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index 5bc53a8..3617165 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -83,7 +83,7 @@
     public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4;
 
     /**
-     * Indicates that the manager weas unable to generate one or more of the requested file
+     * Indicates that the manager was unable to generate one or more of the requested file
      * descriptors.
      * This is a non-fatal result code -- some file descriptors may still be generated, but there
      * is no guarantee that they will be the same number as requested.
@@ -149,7 +149,7 @@
             DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
             String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
             File expectedTokenFile = new File(
-                    MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceInfo()),
+                    MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()),
                     expectedTokenFileName);
             if (!expectedTokenFile.exists()) {
                 Log.w(LOG_TAG, "Supplied download request does not match a token that we have. " +
@@ -201,22 +201,24 @@
 
         Uri destinationUri = request.getDestinationUri();
         Uri finalTempFile = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FINAL_URI);
-        if (!verifyTempFilePath(context, request.getFileServiceInfo(), finalTempFile)) {
+        if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) {
             Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
             setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
             return;
         }
 
-        String relativePath = calculateDestinationFileRelativePath(request,
-                (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO));
+        FileInfo completedFileInfo =
+                (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO);
+        String relativePath = calculateDestinationFileRelativePath(request, completedFileInfo);
 
         Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath);
         if (finalFileLocation == null) {
             Log.w(LOG_TAG, "Failed to move temp file to final destination");
-            // TODO: how do we notify the app of this?
             setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
+            return;
         }
         intentForApp.putExtra(MbmsDownloadManager.EXTRA_COMPLETED_FILE_URI, finalFileLocation);
+        intentForApp.putExtra(MbmsDownloadManager.EXTRA_FILE_INFO, completedFileInfo);
 
         context.sendBroadcast(intentForApp);
         setResultCode(RESULT_OK);
@@ -235,7 +237,7 @@
         }
 
         for (Uri tempFileUri : tempFiles) {
-            if (verifyTempFilePath(context, request.getFileServiceInfo(), tempFileUri)) {
+            if (verifyTempFilePath(context, request.getFileServiceId(), tempFileUri)) {
                 File tempFile = new File(tempFileUri.getSchemeSpecificPart());
                 tempFile.delete();
             }
@@ -276,7 +278,8 @@
     private ArrayList<UriPathPair> generateFreshTempFiles(Context context,
             FileServiceInfo serviceInfo,
             int freshFdCount) {
-        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceInfo);
+        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
+                serviceInfo.getServiceId());
         if (!tempFileDir.exists()) {
             tempFileDir.mkdirs();
         }
@@ -327,7 +330,7 @@
         ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size());
 
         for (Uri fileUri : pausedFiles) {
-            if (!verifyTempFilePath(context, serviceInfo, fileUri)) {
+            if (!verifyTempFilePath(context, serviceInfo.getServiceId(), fileUri)) {
                 Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume");
                 setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
                 continue;
@@ -351,7 +354,8 @@
     private void cleanupTempFiles(Context context, Intent intent) {
         FileServiceInfo serviceInfo =
                 intent.getParcelableExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO);
-        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceInfo);
+        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
+                serviceInfo.getServiceId());
         List<Uri> filesInUse =
                 intent.getParcelableArrayListExtra(MbmsDownloadManager.EXTRA_TEMP_FILES_IN_USE);
         File[] filesToDelete = tempFileDir.listFiles(new FileFilter() {
@@ -439,7 +443,7 @@
         return null;
     }
 
-    private static boolean verifyTempFilePath(Context context, FileServiceInfo serviceInfo,
+    private static boolean verifyTempFilePath(Context context, String serviceId,
             Uri filePath) {
         if (!ContentResolver.SCHEME_FILE.equals(filePath.getScheme())) {
             Log.w(LOG_TAG, "Uri " + filePath + " does not have a file scheme");
@@ -454,7 +458,7 @@
         }
 
         if (!MbmsUtils.isContainedIn(
-                MbmsUtils.getEmbmsTempFileDirForService(context, serviceInfo), tempFile)) {
+                MbmsUtils.getEmbmsTempFileDirForService(context, serviceId), tempFile)) {
             return false;
         }
 
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index b28950e..1e03fb9 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -89,10 +89,9 @@
     /**
      * Returns a File linked to the directory used to store temp files for this file service
      */
-    public static File getEmbmsTempFileDirForService(Context context, FileServiceInfo serviceInfo) {
+    public static File getEmbmsTempFileDirForService(Context context, String serviceId) {
         File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context);
 
-        String tempFileDirName = String.valueOf(serviceInfo.getServiceId());
-        return new File(embmsTempFileDir, tempFileDirName);
+        return new File(embmsTempFileDir, serviceId);
     }
 }
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index b41dadb..9053b23 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -30,6 +30,20 @@
 
 include $(BUILD_JAVA_LIBRARY)
 
+# Build the repackaged.android.test.runner library
+# ================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework legacy-test
+
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../legacy-test/jarjar-rules.txt
+
+LOCAL_MODULE:= repackaged.android.test.runner
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
 # Generate the stub source files for android.test.runner.stubs
 # ============================================================
 include $(CLEAR_VARS)