Merge "Don't accidentally delete renamed packages"
diff --git a/Android.bp b/Android.bp
index dba49ce..9088315 100644
--- a/Android.bp
+++ b/Android.bp
@@ -17,3 +17,7 @@
     "native/android",
     "native/graphics/jni",
 ]
+
+optional_subdirs = [
+    "core/tests/utiltests/jni",
+]
diff --git "a/\135" "b/\135"
deleted file mode 100644
index 5619151..0000000
--- "a/\135"
+++ /dev/null
@@ -1,12 +0,0 @@
-NetworkNotificationManager: logging improvements
-
-TODO: squash me
-# Please enter the commit message for your changes. Lines starting
-# with '#' will be ignored, and an empty message aborts the commit.
-# On branch notification_tagging
-# Your branch is ahead of 'goog/master' by 2 commits.
-#   (use "git push" to publish your local commits)
-#
-# Changes to be committed:
-#	modified:   services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
-#
diff --git a/api/current.txt b/api/current.txt
index d158e10..5a2df57 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3479,6 +3479,7 @@
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
     method public void enterPictureInPictureMode();
     method public void enterPictureInPictureMode(float);
+    method public void enterPictureInPictureModeOnMoveToBackground(boolean);
     method public android.view.View findViewById(int);
     method public void finish();
     method public void finishActivity(int);
@@ -37294,7 +37295,9 @@
     field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
     field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
     field public static final java.lang.String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
+    field public static final java.lang.String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
     field public static final java.lang.String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+    field public static final java.lang.String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
     field public static final java.lang.String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
     field public static final java.lang.String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
     field public static final java.lang.String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars";
diff --git a/api/system-current.txt b/api/system-current.txt
index 9d48713..7a287df 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3596,6 +3596,7 @@
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
     method public void enterPictureInPictureMode();
     method public void enterPictureInPictureMode(float);
+    method public void enterPictureInPictureModeOnMoveToBackground(boolean);
     method public android.view.View findViewById(int);
     method public void finish();
     method public void finishActivity(int);
@@ -40398,7 +40399,9 @@
     field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
     field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
     field public static final java.lang.String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
+    field public static final java.lang.String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
     field public static final java.lang.String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+    field public static final java.lang.String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
     field public static final java.lang.String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
     field public static final java.lang.String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
     field public static final java.lang.String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars";
diff --git a/api/test-current.txt b/api/test-current.txt
index 9cd08d5..20b723d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3481,6 +3481,7 @@
     method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
     method public void enterPictureInPictureMode();
     method public void enterPictureInPictureMode(float);
+    method public void enterPictureInPictureModeOnMoveToBackground(boolean);
     method public android.view.View findViewById(int);
     method public void finish();
     method public void finishActivity(int);
@@ -37391,7 +37392,9 @@
     field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
     field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
     field public static final java.lang.String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
+    field public static final java.lang.String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
     field public static final java.lang.String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+    field public static final java.lang.String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
     field public static final java.lang.String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
     field public static final java.lang.String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
     field public static final java.lang.String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars";
@@ -41525,11 +41528,15 @@
   public final class ProtoOutputStream {
     ctor public ProtoOutputStream();
     ctor public ProtoOutputStream(int);
+    ctor public ProtoOutputStream(java.io.OutputStream);
+    ctor public ProtoOutputStream(java.io.FileDescriptor);
     method public static int checkFieldId(long, long);
     method public static int convertObjectIdToOrdinal(int);
     method public void dump(java.lang.String);
+    method public void end(long);
     method public void endObject(long);
     method public void endRepeatedObject(long);
+    method public void flush();
     method public byte[] getBytes();
     method public static int getDepthFromToken(long);
     method public static int getObjectIdFromToken(long);
@@ -41538,9 +41545,17 @@
     method public static int getTagSizeFromToken(long);
     method public static long makeFieldId(int, long);
     method public static long makeToken(int, boolean, int, int, int);
+    method public long start(long);
     method public long startObject(long);
     method public long startRepeatedObject(long);
     method public static java.lang.String token2String(long);
+    method public void write(long, double);
+    method public void write(long, float);
+    method public void write(long, int);
+    method public void write(long, long);
+    method public void write(long, boolean);
+    method public void write(long, java.lang.String);
+    method public void write(long, byte[]);
     method public void writeBool(long, boolean);
     method public void writeBytes(long, byte[]);
     method public void writeDouble(long, double);
@@ -41550,6 +41565,8 @@
     method public void writeFloat(long, float);
     method public void writeInt32(long, int);
     method public void writeInt64(long, long);
+    method public void writeObject(long, byte[]);
+    method public void writeObjectImpl(int, byte[]);
     method public void writePackedBool(long, boolean[]);
     method public void writePackedDouble(long, double[]);
     method public void writePackedEnum(long, int[]);
@@ -41573,6 +41590,8 @@
     method public void writeRepeatedFloat(long, float);
     method public void writeRepeatedInt32(long, int);
     method public void writeRepeatedInt64(long, long);
+    method public void writeRepeatedObject(long, byte[]);
+    method public void writeRepeatedObjectImpl(int, byte[]);
     method public void writeRepeatedSFixed32(long, int);
     method public void writeRepeatedSFixed64(long, long);
     method public void writeRepeatedSInt32(long, int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index eb73e36..6dd488f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2030,6 +2030,28 @@
     }
 
     /**
+     * Requests to the system that the activity can be automatically put into picture-in-picture
+     * mode when the user leaves the activity causing it normally to be hidden.  This is a *not*
+     * a guarantee that the activity will actually be put in picture-in-picture mode, and depends
+     * on a number of factors, including whether there is already something in picture-in-picture.
+     *
+     * If {@param enterPictureInPictureOnMoveToBg} is true, then you may also call
+     * {@link #setPictureInPictureAspectRatio(float)} to specify the aspect ratio to automatically
+     * enter picture-in-picture with.
+     *
+     * @param enterPictureInPictureOnMoveToBg whether or not this activity can automatically enter
+     *                                     picture-in-picture
+     */
+    public void enterPictureInPictureModeOnMoveToBackground(
+            boolean enterPictureInPictureOnMoveToBg) {
+        try {
+            ActivityManagerNative.getDefault().enterPictureInPictureModeOnMoveToBackground(mToken,
+                    enterPictureInPictureOnMoveToBg);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Called by the system when the device configuration changes while your
      * activity is running.  Note that this will <em>only</em> be called if
      * you have selected configurations you would like to handle with the
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 640ca6c..62b3977 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -468,6 +468,10 @@
     boolean isInPictureInPictureMode(in IBinder token);
     void killPackageDependents(in String packageName, int userId);
     void enterPictureInPictureMode(in IBinder token);
+    void enterPictureInPictureModeWithAspectRatio(in IBinder token, float aspectRatio);
+    void enterPictureInPictureModeOnMoveToBackground(in IBinder token,
+            boolean enterPictureInPictureOnMoveToBg);
+    void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio);
     void activityRelaunched(in IBinder token);
     IBinder getUriPermissionOwnerForActivity(in IBinder activityToken);
     /**
@@ -568,8 +572,6 @@
     boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId);
     void unregisterTaskStackListener(ITaskStackListener listener);
     void moveStackToDisplay(int stackId, int displayId);
-    void enterPictureInPictureModeWithAspectRatio(in IBinder token, float aspectRatio);
-    void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio);
     boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras,
             in IBinder activityToken);
     void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
@@ -578,4 +580,4 @@
     // side. If so, make sure they are using the correct transaction ids.
     // If a transaction which will also be used on the native side is being inserted, add it
     // alongside with other transactions of this kind at the top of this file.
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java
index dffa969..85ca012 100644
--- a/core/java/android/app/MediaRouteActionProvider.java
+++ b/core/java/android/app/MediaRouteActionProvider.java
@@ -119,7 +119,6 @@
         }
 
         mButton = new MediaRouteButton(mContext);
-        mButton.setCheatSheetEnabled(true);
         mButton.setRouteTypes(mRouteTypes);
         mButton.setExtendedSettingsClickListener(mExtendedSettingsListener);
         mButton.setLayoutParams(new ViewGroup.LayoutParams(
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index 70a5e15..09e95df 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -24,18 +24,13 @@
 import android.content.ContextWrapper;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRouter;
 import android.media.MediaRouter.RouteGroup;
 import android.media.MediaRouter.RouteInfo;
-import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
 import android.view.SoundEffectConstants;
 import android.view.View;
-import android.widget.Toast;
 
 public class MediaRouteButton extends View {
     private final MediaRouter mRouter;
@@ -47,7 +42,6 @@
 
     private Drawable mRemoteIndicator;
     private boolean mRemoteActive;
-    private boolean mCheatSheetEnabled;
     private boolean mIsConnecting;
 
     private int mMinWidth;
@@ -98,7 +92,6 @@
         a.recycle();
 
         setClickable(true);
-        setLongClickable(true);
 
         setRouteTypes(routeTypes);
     }
@@ -178,12 +171,10 @@
         throw new IllegalStateException("The MediaRouteButton's Context is not an Activity.");
     }
 
-    /**
-     * Sets whether to enable showing a toast with the content descriptor of the
-     * button when the button is long pressed.
-     */
-    void setCheatSheetEnabled(boolean enable) {
-        mCheatSheetEnabled = enable;
+    @Override
+    public void setContentDescription(CharSequence contentDescription) {
+        super.setContentDescription(contentDescription);
+        setTooltip(contentDescription);
     }
 
     @Override
@@ -197,47 +188,6 @@
     }
 
     @Override
-    public boolean performLongClick() {
-        if (super.performLongClick()) {
-            return true;
-        }
-
-        if (!mCheatSheetEnabled) {
-            return false;
-        }
-
-        final CharSequence contentDesc = getContentDescription();
-        if (TextUtils.isEmpty(contentDesc)) {
-            // Don't show the cheat sheet if we have no description
-            return false;
-        }
-
-        final int[] screenPos = new int[2];
-        final Rect displayFrame = new Rect();
-        getLocationOnScreen(screenPos);
-        getWindowVisibleDisplayFrame(displayFrame);
-
-        final Context context = getContext();
-        final int width = getWidth();
-        final int height = getHeight();
-        final int midy = screenPos[1] + height / 2;
-        final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-
-        Toast cheatSheet = Toast.makeText(context, contentDesc, Toast.LENGTH_SHORT);
-        if (midy < displayFrame.height()) {
-            // Show along the top; follow action buttons
-            cheatSheet.setGravity(Gravity.TOP | Gravity.END,
-                    screenWidth - screenPos[0] - width / 2, height);
-        } else {
-            // Show along the bottom center
-            cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
-        }
-        cheatSheet.show();
-        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-        return true;
-    }
-
-    @Override
     protected int[] onCreateDrawableState(int extraSpace) {
         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
 
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index c1180e2..c5a8288 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -26,6 +26,8 @@
 import android.util.Log;
 
 import com.google.android.collect.Maps;
+
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.XmlUtils;
 
 import dalvik.system.BlockGuard;
@@ -72,6 +74,14 @@
     private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
             new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
 
+    /** Current memory state (always increasing) */
+    @GuardedBy("this")
+    private long mCurrentMemoryStateGeneration;
+
+    /** Latest memory state that was committed to disk */
+    @GuardedBy("mWritingToDiskLock")
+    private long mDiskStateGeneration;
+
     SharedPreferencesImpl(File file, int mode) {
         mFile = file;
         mBackupFile = makeBackupFile(file);
@@ -289,7 +299,7 @@
 
     // Return value from EditorImpl#commitToMemory()
     private static class MemoryCommitResult {
-        public boolean changesMade;  // any keys different?
+        public long memoryStateGeneration;
         public List<String> keysModified;  // may be null
         public Set<OnSharedPreferenceChangeListener> listeners;  // may be null
         public Map<?, ?> mapToWriteToDisk;
@@ -412,9 +422,11 @@
                 }
 
                 synchronized (this) {
+                    boolean changesMade = false;
+
                     if (mClear) {
                         if (!mMap.isEmpty()) {
-                            mcr.changesMade = true;
+                            changesMade = true;
                             mMap.clear();
                         }
                         mClear = false;
@@ -441,13 +453,19 @@
                             mMap.put(k, v);
                         }
 
-                        mcr.changesMade = true;
+                        changesMade = true;
                         if (hasListeners) {
                             mcr.keysModified.add(k);
                         }
                     }
 
                     mModified.clear();
+
+                    if (changesMade) {
+                        mCurrentMemoryStateGeneration++;
+                    }
+
+                    mcr.memoryStateGeneration = mCurrentMemoryStateGeneration;
                 }
             }
             return mcr;
@@ -509,10 +527,12 @@
      */
     private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                   final Runnable postWriteRunnable) {
+        final boolean isFromSyncCommit = (postWriteRunnable == null);
+
         final Runnable writeToDiskRunnable = new Runnable() {
                 public void run() {
                     synchronized (mWritingToDiskLock) {
-                        writeToFile(mcr);
+                        writeToFile(mcr, isFromSyncCommit);
                     }
                     synchronized (SharedPreferencesImpl.this) {
                         mDiskWritesInFlight--;
@@ -523,8 +543,6 @@
                 }
             };
 
-        final boolean isFromSyncCommit = (postWriteRunnable == null);
-
         // Typical #commit() path with fewer allocations, doing a write on
         // the current thread.
         if (isFromSyncCommit) {
@@ -538,6 +556,10 @@
             }
         }
 
+        if (DEBUG) {
+            Log.d(TAG, "added " + mcr.memoryStateGeneration + " -> " + mFile.getName());
+        }
+
         QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
     }
 
@@ -565,17 +587,34 @@
     }
 
     // Note: must hold mWritingToDiskLock
-    private void writeToFile(MemoryCommitResult mcr) {
+    private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
         // Rename the current file so it may be used as a backup during the next read
         if (mFile.exists()) {
-            if (!mcr.changesMade) {
-                // If the file already exists, but no changes were
-                // made to the underlying map, it's wasteful to
-                // re-write the file.  Return as if we wrote it
-                // out.
+            boolean needsWrite = false;
+
+            if (isFromSyncCommit) {
+                // Only need to write if the disk state is older than this commit
+                if (mDiskStateGeneration < mcr.memoryStateGeneration) {
+                    needsWrite = true;
+                }
+            } else {
+                synchronized (this) {
+                    // No need to persist intermediate states. Just wait for the latest state to be
+                    // persisted.
+                    if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
+                        needsWrite = true;
+                    }
+                }
+            }
+
+            if (!needsWrite) {
+                if (DEBUG) {
+                    Log.d(TAG, "skipped " + mcr.memoryStateGeneration + " -> " + mFile.getName());
+                }
                 mcr.setDiskWriteResult(true);
                 return;
             }
+
             if (!mBackupFile.exists()) {
                 if (!mFile.renameTo(mBackupFile)) {
                     Log.e(TAG, "Couldn't rename file " + mFile
@@ -599,6 +638,11 @@
             }
             XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
             FileUtils.sync(str);
+
+            if (DEBUG) {
+                Log.d(TAG, "wrote " + mcr.memoryStateGeneration + " -> " + mFile.getName());
+            }
+
             str.close();
             ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
             try {
@@ -612,7 +656,11 @@
             }
             // Writing was successful, delete the backup file if there is one.
             mBackupFile.delete();
+
+            mDiskStateGeneration = mcr.memoryStateGeneration;
+
             mcr.setDiskWriteResult(true);
+
             return;
         } catch (XmlPullParserException e) {
             Log.w(TAG, "writeToFile: Got exception:", e);
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index e2ebd46..5d90acc 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -182,12 +182,30 @@
      */
     public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE = 3;
     /**
-     * Activity is does not support resizing, but we are forcing it to be resizeable. Only affects
+     * Activity does not support resizing, but we are forcing it to be resizeable. Only affects
      * certain pre-N apps where we force them to be resizeable.
      * @hide
      */
     public static final int RESIZE_MODE_FORCE_RESIZEABLE = 4;
     /**
+     * Activity does not support resizing, but we are forcing it to be resizeable as long
+     * as the size remains landscape.
+     * @hide
+     */
+    public static final int RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY = 5;
+    /**
+     * Activity does not support resizing, but we are forcing it to be resizeable as long
+     * as the size remains portrait.
+     * @hide
+     */
+    public static final int RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY = 6;
+    /**
+     * Activity does not support resizing, but we are forcing it to be resizeable as long
+     * as the bounds remain in the same orientation as they are.
+     * @hide
+     */
+    public static final int RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION = 7;
+    /**
      * Value indicating if the resizing mode the activity supports.
      * See {@link android.R.attr#resizeableActivity}.
      * @hide
@@ -859,26 +877,51 @@
      * @hide
      */
     boolean isFixedOrientation() {
-        return screenOrientation == SCREEN_ORIENTATION_LANDSCAPE
-                || screenOrientation == SCREEN_ORIENTATION_PORTRAIT
-                || screenOrientation == SCREEN_ORIENTATION_SENSOR_LANDSCAPE
-                || screenOrientation == SCREEN_ORIENTATION_SENSOR_PORTRAIT
-                || screenOrientation == SCREEN_ORIENTATION_REVERSE_LANDSCAPE
-                || screenOrientation == SCREEN_ORIENTATION_REVERSE_PORTRAIT
-                || screenOrientation == SCREEN_ORIENTATION_USER_LANDSCAPE
-                || screenOrientation == SCREEN_ORIENTATION_USER_PORTRAIT
+        return isFixedOrientationLandscape() || isFixedOrientationPortrait()
                 || screenOrientation == SCREEN_ORIENTATION_LOCKED;
     }
 
+    /**
+     * Returns true if the activity's orientation is fixed to landscape.
+     * @hide
+     */
+    boolean isFixedOrientationLandscape() {
+        return screenOrientation == SCREEN_ORIENTATION_LANDSCAPE
+                || screenOrientation == SCREEN_ORIENTATION_SENSOR_LANDSCAPE
+                || screenOrientation == SCREEN_ORIENTATION_REVERSE_LANDSCAPE
+                || screenOrientation == SCREEN_ORIENTATION_USER_LANDSCAPE;
+    }
+
+    /**
+     * Returns true if the activity's orientation is fixed to portrait.
+     * @hide
+     */
+    boolean isFixedOrientationPortrait() {
+        return screenOrientation == SCREEN_ORIENTATION_PORTRAIT
+                || screenOrientation == SCREEN_ORIENTATION_SENSOR_PORTRAIT
+                || screenOrientation == SCREEN_ORIENTATION_REVERSE_PORTRAIT
+                || screenOrientation == SCREEN_ORIENTATION_USER_PORTRAIT;
+    }
+
     /** @hide */
     public static boolean isResizeableMode(int mode) {
         return mode == RESIZE_MODE_RESIZEABLE
                 || mode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE
                 || mode == RESIZE_MODE_FORCE_RESIZEABLE
+                || mode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
+                || mode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
+                || mode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION
                 || mode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
     }
 
     /** @hide */
+    public static boolean isPreserveOrientationMode(int mode) {
+        return mode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
+                || mode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
+                || mode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+    }
+
+    /** @hide */
     public static String resizeModeToString(int mode) {
         switch (mode) {
             case RESIZE_MODE_UNRESIZEABLE:
@@ -891,6 +934,12 @@
                 return "RESIZE_MODE_RESIZEABLE_AND_PIPABLE";
             case RESIZE_MODE_FORCE_RESIZEABLE:
                 return "RESIZE_MODE_FORCE_RESIZEABLE";
+            case RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY:
+                return "RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY";
+            case RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY:
+                return "RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY";
+            case RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION:
+                return "RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION";
             default:
                 return "unknown=" + mode;
         }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index f5eb4bd..9b2dd68 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -88,6 +88,9 @@
 import static android.content.pm.ActivityInfo.FLAG_IMMERSIVE;
 import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
@@ -3896,8 +3899,15 @@
 
         // resize preference isn't set and target sdk version doesn't support resizing apps by
         // default. For the app to be resizeable if it isn't fixed orientation or immersive.
-        aInfo.resizeMode = (aInfo.isFixedOrientation() || (aInfo.flags & FLAG_IMMERSIVE) != 0)
-                ? RESIZE_MODE_UNRESIZEABLE : RESIZE_MODE_FORCE_RESIZEABLE;
+        if (aInfo.isFixedOrientationPortrait()) {
+            aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+        } else if (aInfo.isFixedOrientationLandscape()) {
+            aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+        } else if (aInfo.isFixedOrientation()) {
+            aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+        } else {
+            aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
+        }
     }
 
     private void parseLayout(Resources res, AttributeSet attrs, Activity a) {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 3e79118..1a05904 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -348,9 +348,7 @@
 
                     Size[] sizes = streamConfigurations.getOutputSizes(surfaceType);
                     if (sizes == null) {
-                        // WAR: Override default format to IMPLEMENTATION_DEFINED for b/9487482
-                        if ((surfaceType >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
-                            surfaceType <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
+                        if (surfaceType == ImageFormat.PRIVATE) {
 
                             // YUV_420_888 is always present in LEGACY for all
                             // IMPLEMENTATION_DEFINED output sizes, and is publicly visible in the
@@ -649,7 +647,16 @@
      */
     public static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
         checkNotNull(surface);
-        return LegacyExceptionUtils.throwOnError(nativeDetectSurfaceType(surface));
+        int surfaceType = nativeDetectSurfaceType(surface);
+
+        // TODO: remove this override since the default format should be
+        // ImageFormat.PRIVATE. b/9487482
+        if ((surfaceType >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
+                surfaceType <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
+            surfaceType = ImageFormat.PRIVATE;
+        }
+
+        return LegacyExceptionUtils.throwOnError(surfaceType);
     }
 
     /**
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index dfa19b0..dbe1394 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -408,12 +408,6 @@
         // See if consumer is flexible.
         boolean isFlexible = SurfaceUtils.isFlexibleConsumer(surface);
 
-        // Override RGB formats to IMPLEMENTATION_DEFINED, b/9487482
-        if ((surfaceFormat >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
-                        surfaceFormat <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
-            surfaceFormat = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
-        }
-
         StreamConfiguration[] configs =
                 surfaceDataspace != HAL_DATASPACE_DEPTH ? mConfigurations : mDepthConfigurations;
         for (StreamConfiguration config : configs) {
diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
index 4b958df..e1e1c4f 100644
--- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java
+++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
@@ -118,15 +118,7 @@
      * @param surface The high speed output surface to be checked.
      */
     private static void checkHighSpeedSurfaceFormat(Surface surface) {
-        // TODO: remove this override since the default format should be
-        // ImageFormat.PRIVATE. b/9487482
-        final int HAL_FORMAT_RGB_START = 1; // HAL_PIXEL_FORMAT_RGBA_8888 from graphics.h
-        final int HAL_FORMAT_RGB_END = 5; // HAL_PIXEL_FORMAT_BGRA_8888 from graphics.h
         int surfaceFormat = SurfaceUtils.getSurfaceFormat(surface);
-        if (surfaceFormat >= HAL_FORMAT_RGB_START &&
-                surfaceFormat <= HAL_FORMAT_RGB_END) {
-            surfaceFormat = ImageFormat.PRIVATE;
-        }
 
         if (surfaceFormat != ImageFormat.PRIVATE) {
             throw new IllegalArgumentException("Surface format(" + surfaceFormat + ") is not"
diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl
index 59cbf6e..542a0a7 100644
--- a/core/java/android/net/INetworkScoreService.aidl
+++ b/core/java/android/net/INetworkScoreService.aidl
@@ -56,17 +56,26 @@
     void disableScoring();
 
     /**
-     * Register a network subsystem for scoring.
+     * Register a cache to receive scoring updates.
      *
      * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
      * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
      * @throws SecurityException if the caller is not the system.
-     * @throws IllegalArgumentException if a score cache is already registed for this type.
      * @hide
      */
     void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache);
 
     /**
+     * Unregister a cache to receive scoring updates.
+     *
+     * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
+     * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
+     * @throws SecurityException if the caller is not the system.
+     * @hide
+     */
+    void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache);
+
+    /**
      * Request a recommendation for the best network to connect to
      * taking into account the inputs from the {@link RecommendationRequest}.
      *
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index a2d2b58..236c6fc 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -279,6 +279,24 @@
     }
 
     /**
+     * Unregister a network score cache.
+     *
+     * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
+     * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
+     * @throws SecurityException if the caller does not hold the
+     *         {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission.
+     * @throws IllegalArgumentException if a score cache is already registered for this type.
+     * @hide
+     */
+    public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+        try {
+            mService.unregisterNetworkScoreCache(networkType, scoreCache);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Request a recommendation for which network to connect to.
      *
      * @param request a {@link RecommendationRequest} instance containing additional
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 686938f..5006433 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7757,6 +7757,8 @@
 
         /**
          * Value to specify if Wi-Fi Wakeup feature is enabled.
+         *
+         * Type: int (0 for false, 1 for true)
          * @hide
          */
         @SystemApi
@@ -7765,6 +7767,8 @@
         /**
          * Value to specify if network recommendations from
          * {@link com.android.server.NetworkScoreService} are enabled.
+         *
+         * Type: int (0 for false, 1 for true)
          * @hide
          */
         @SystemApi
diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java
index ccaf204..749cf08 100644
--- a/core/java/android/util/MemoryIntArray.java
+++ b/core/java/android/util/MemoryIntArray.java
@@ -19,7 +19,6 @@
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
-import android.os.Process;
 
 import libcore.io.IoUtils;
 import dalvik.system.CloseGuard;
@@ -37,13 +36,13 @@
  * each other.
  * <p>
  * The data structure is designed to have one owner process that can
- * read/write. There may be multiple client processes that can only read or
- * read/write depending how the data structure was configured when
- * instantiated. The owner process is the process that created the array.
- * The shared memory is pinned (not reclaimed by the system) until the
- * owning process dies or the data structure is closed. This class
- * is <strong>not</strong> thread safe. You should not interact with
- * an instance of this class once it is closed.
+ * read/write. There may be multiple client processes that can only read.
+ * The owner process is the process that created the array. The shared
+ * memory is pinned (not reclaimed by the system) until the owning process
+ * dies or the data structure is closed. This class is <strong>not</strong>
+ * thread safe. You should not interact with an instance of this class
+ * once it is closed. If you pass back to the owner process an instance
+ * it will be read only even in the owning process.
  * </p>
  *
  * @hide
@@ -55,8 +54,7 @@
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
-    private final int mOwnerPid;
-    private final boolean mClientWritable;
+    private final boolean mIsOwner;
     private final long mMemoryAddr;
     private int mFd;
 
@@ -65,35 +63,27 @@
      *
      * @param size The size of the array in terms of integer slots. Cannot be
      *     more than {@link #getMaxSize()}.
-     * @param clientWritable Whether other processes can write to the array.
      * @throws IOException If an error occurs while accessing the shared memory.
      */
-    public MemoryIntArray(int size, boolean clientWritable) throws IOException {
+    public MemoryIntArray(int size) throws IOException {
         if (size > MAX_SIZE) {
             throw new IllegalArgumentException("Max size is " + MAX_SIZE);
         }
-        mOwnerPid = Process.myPid();
-        mClientWritable = clientWritable;
+        mIsOwner = true;
         final String name = UUID.randomUUID().toString();
         mFd = nativeCreate(name, size);
-        mMemoryAddr = nativeOpen(mFd, true, clientWritable);
+        mMemoryAddr = nativeOpen(mFd, mIsOwner);
         mCloseGuard.open("close");
     }
 
     private MemoryIntArray(Parcel parcel) throws IOException {
-        mOwnerPid = parcel.readInt();
-        mClientWritable = (parcel.readInt() == 1);
+        mIsOwner = false;
         ParcelFileDescriptor pfd = parcel.readParcelable(null);
         if (pfd == null) {
             throw new IOException("No backing file descriptor");
         }
         mFd = pfd.detachFd();
-        final long memoryAddress = parcel.readLong();
-        if (isOwner()) {
-            mMemoryAddr = memoryAddress;
-        } else {
-            mMemoryAddr = nativeOpen(mFd, false, mClientWritable);
-        }
+        mMemoryAddr = nativeOpen(mFd, mIsOwner);
         mCloseGuard.open("close");
     }
 
@@ -102,7 +92,7 @@
      */
     public boolean isWritable() {
         enforceNotClosed();
-        return isOwner() || mClientWritable;
+        return mIsOwner;
     }
 
     /**
@@ -115,7 +105,7 @@
     public int get(int index) throws IOException {
         enforceNotClosed();
         enforceValidIndex(index);
-        return nativeGet(mFd, mMemoryAddr, index, isOwner());
+        return nativeGet(mFd, mMemoryAddr, index);
     }
 
     /**
@@ -131,7 +121,7 @@
         enforceNotClosed();
         enforceWritable();
         enforceValidIndex(index);
-        nativeSet(mFd, mMemoryAddr, index, value, isOwner());
+        nativeSet(mFd, mMemoryAddr, index, value);
     }
 
     /**
@@ -152,7 +142,7 @@
     @Override
     public void close() throws IOException {
         if (!isClosed()) {
-            nativeClose(mFd, mMemoryAddr, isOwner());
+            nativeClose(mFd, mMemoryAddr, mIsOwner);
             mFd = -1;
             mCloseGuard.close();
         }
@@ -184,11 +174,8 @@
     public void writeToParcel(Parcel parcel, int flags) {
         ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(mFd);
         try {
-            parcel.writeInt(mOwnerPid);
-            parcel.writeInt(mClientWritable ? 1 : 0);
             // Don't let writing to a parcel to close our fd - plz
             parcel.writeParcelable(pfd, flags & ~Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
-            parcel.writeLong(mMemoryAddr);
         } finally {
             pfd.detachFd();
         }
@@ -214,10 +201,6 @@
         return mFd;
     }
 
-    private boolean isOwner() {
-        return mOwnerPid == Process.myPid();
-    }
-
     private void enforceNotClosed() {
         if (isClosed()) {
             throw new IllegalStateException("cannot interact with a closed instance");
@@ -239,10 +222,10 @@
     }
 
     private native int nativeCreate(String name, int size);
-    private native long nativeOpen(int fd, boolean owner, boolean writable);
+    private native long nativeOpen(int fd, boolean owner);
     private native void nativeClose(int fd, long memoryAddr, boolean owner);
-    private native int nativeGet(int fd, long memoryAddr, int index, boolean owner);
-    private native void nativeSet(int fd, long memoryAddr, int index, int value, boolean owner);
+    private native int nativeGet(int fd, long memoryAddr, int index);
+    private native void nativeSet(int fd, long memoryAddr, int index, int value);
     private native int nativeSize(int fd);
 
     /**
@@ -259,8 +242,7 @@
             try {
                 return new MemoryIntArray(parcel);
             } catch (IOException ioe) {
-                Log.e(TAG, "Error unparceling MemoryIntArray");
-                return null;
+                throw new IllegalArgumentException("Error unparceling MemoryIntArray");
             }
         }
 
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
index 8f99399..81251fc 100644
--- a/core/java/android/util/proto/ProtoOutputStream.java
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -19,6 +19,10 @@
 import android.annotation.TestApi;
 import android.util.Log;
 
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.util.List;
 
@@ -179,6 +183,11 @@
     private EncodedBuffer mBuffer;
 
     /**
+     * Our stream.  If there is one.
+     */
+    private OutputStream mStream;
+
+    /**
      * Current nesting depth of startObject calls.
      */
     private int mDepth;
@@ -226,6 +235,690 @@
         mBuffer = new EncodedBuffer(chunkSize);
     }
 
+    /**
+     * Construct a ProtoOutputStream that sits on top of an OutputStream.
+     * @more
+     * The {@link #flush() flush()} method must be called when done writing
+     * to flush any remanining data, althought data *may* be written at intermediate
+     * points within the writing as well.
+     */
+    public ProtoOutputStream(OutputStream stream) {
+        this();
+        mStream = stream;
+    }
+
+    /**
+     * Construct a ProtoOutputStream that sits on top of a FileDescriptor.
+     * @more
+     * The {@link #flush() flush()} method must be called when done writing
+     * to flush any remanining data, althought data *may* be written at intermediate
+     * points within the writing as well.
+     */
+    public ProtoOutputStream(FileDescriptor fd) {
+        this(new FileOutputStream(fd));
+    }
+
+    /**
+     * Write a value for the given fieldId.
+     *
+     * Will automatically convert for the following field types, and
+     * throw an exception for others: double, float, int32, int64, uint32, uint64,
+     * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, double val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // double
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeDoubleImpl(id, (double)val);
+                break;
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedDoubleImpl(id, (double)val);
+                break;
+            // float
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFloatImpl(id, (float)val);
+                break;
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFloatImpl(id, (float)val);
+                break;
+            // int32
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt32Impl(id, (int)val);
+                break;
+            // int64
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt64Impl(id, (long)val);
+                break;
+            // uint32
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt32Impl(id, (int)val);
+                break;
+            // uint64
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt64Impl(id, (long)val);
+                break;
+            // sint32
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt32Impl(id, (int)val);
+                break;
+            // sint64
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt64Impl(id, (long)val);
+                break;
+            // fixed32
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed32Impl(id, (int)val);
+                break;
+            // fixed64
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed64Impl(id, (long)val);
+                break;
+            // sfixed32
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed32Impl(id, (int)val);
+                break;
+            // sfixed64
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed64Impl(id, (long)val);
+                break;
+            // bool
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBoolImpl(id, val != 0);
+                break;
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBoolImpl(id, val != 0);
+                break;
+            // enum
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeEnumImpl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedEnumImpl(id, (int)val);
+                break;
+            // string, bytes, object not allowed here.
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, double) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a value for the given fieldId.
+     *
+     * Will automatically convert for the following field types, and
+     * throw an exception for others: double, float, int32, int64, uint32, uint64,
+     * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, float val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // double
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeDoubleImpl(id, (double)val);
+                break;
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedDoubleImpl(id, (double)val);
+                break;
+            // float
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFloatImpl(id, (float)val);
+                break;
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFloatImpl(id, (float)val);
+                break;
+            // int32
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt32Impl(id, (int)val);
+                break;
+            // int64
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt64Impl(id, (long)val);
+                break;
+            // uint32
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt32Impl(id, (int)val);
+                break;
+            // uint64
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt64Impl(id, (long)val);
+                break;
+            // sint32
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt32Impl(id, (int)val);
+                break;
+            // sint64
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt64Impl(id, (long)val);
+                break;
+            // fixed32
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed32Impl(id, (int)val);
+                break;
+            // fixed64
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed64Impl(id, (long)val);
+                break;
+            // sfixed32
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed32Impl(id, (int)val);
+                break;
+            // sfixed64
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed64Impl(id, (long)val);
+                break;
+            // bool
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBoolImpl(id, val != 0);
+                break;
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBoolImpl(id, val != 0);
+                break;
+            // enum
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeEnumImpl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedEnumImpl(id, (int)val);
+                break;
+            // string, bytes, object not allowed here.
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, float) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a value for the given fieldId.
+     *
+     * Will automatically convert for the following field types, and
+     * throw an exception for others: double, float, int32, int64, uint32, uint64,
+     * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, int val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // double
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeDoubleImpl(id, (double)val);
+                break;
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedDoubleImpl(id, (double)val);
+                break;
+            // float
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFloatImpl(id, (float)val);
+                break;
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFloatImpl(id, (float)val);
+                break;
+            // int32
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt32Impl(id, (int)val);
+                break;
+            // int64
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt64Impl(id, (long)val);
+                break;
+            // uint32
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt32Impl(id, (int)val);
+                break;
+            // uint64
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt64Impl(id, (long)val);
+                break;
+            // sint32
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt32Impl(id, (int)val);
+                break;
+            // sint64
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt64Impl(id, (long)val);
+                break;
+            // fixed32
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed32Impl(id, (int)val);
+                break;
+            // fixed64
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed64Impl(id, (long)val);
+                break;
+            // sfixed32
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed32Impl(id, (int)val);
+                break;
+            // sfixed64
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed64Impl(id, (long)val);
+                break;
+            // bool
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBoolImpl(id, val != 0);
+                break;
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBoolImpl(id, val != 0);
+                break;
+            // enum
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeEnumImpl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedEnumImpl(id, (int)val);
+                break;
+            // string, bytes, object not allowed here.
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, int) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a value for the given fieldId.
+     *
+     * Will automatically convert for the following field types, and
+     * throw an exception for others: double, float, int32, int64, uint32, uint64,
+     * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, long val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // double
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeDoubleImpl(id, (double)val);
+                break;
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedDoubleImpl(id, (double)val);
+                break;
+            // float
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFloatImpl(id, (float)val);
+                break;
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFloatImpl(id, (float)val);
+                break;
+            // int32
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt32Impl(id, (int)val);
+                break;
+            // int64
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedInt64Impl(id, (long)val);
+                break;
+            // uint32
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt32Impl(id, (int)val);
+                break;
+            // uint64
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeUInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedUInt64Impl(id, (long)val);
+                break;
+            // sint32
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt32Impl(id, (int)val);
+                break;
+            // sint64
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSInt64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSInt64Impl(id, (long)val);
+                break;
+            // fixed32
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed32Impl(id, (int)val);
+                break;
+            // fixed64
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedFixed64Impl(id, (long)val);
+                break;
+            // sfixed32
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed32Impl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed32Impl(id, (int)val);
+                break;
+            // sfixed64
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeSFixed64Impl(id, (long)val);
+                break;
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedSFixed64Impl(id, (long)val);
+                break;
+            // bool
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBoolImpl(id, val != 0);
+                break;
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBoolImpl(id, val != 0);
+                break;
+            // enum
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeEnumImpl(id, (int)val);
+                break;
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedEnumImpl(id, (int)val);
+                break;
+            // string, bytes, object not allowed here.
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, long) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a boolean value for the given fieldId.
+     *
+     * If the field is not a bool field, an exception will be thrown.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, boolean val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // bool
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBoolImpl(id, val);
+                break;
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBoolImpl(id, val);
+                break;
+            // nothing else allowed
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, boolean) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a string value for the given fieldId.
+     *
+     * If the field is not a string field, an exception will be thrown.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, String val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // string
+            case (int)((FIELD_TYPE_STRING | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeStringImpl(id, val);
+                break;
+            case (int)((FIELD_TYPE_STRING | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_STRING | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedStringImpl(id, val);
+                break;
+            // nothing else allowed
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, String) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Write a byte[] value for the given fieldId.
+     *
+     * If the field is not a bytes or object field, an exception will be thrown.
+     *
+     * @param fieldId The field identifier constant from the generated class.
+     * @param val The value.
+     */
+    public void write(long fieldId, byte[] val) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+            // bytes
+            case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeBytesImpl(id, val);
+                break;
+            case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedBytesImpl(id, val);
+                break;
+            // Object
+            case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+                writeObjectImpl(id, val);
+                break;
+            case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+            case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+                writeRepeatedObjectImpl(id, val);
+                break;
+            // nothing else allowed
+            default: {
+                throw new IllegalArgumentException("Attempt to call write(long, byte[]) with "
+                        + getFieldIdString(fieldId));
+            }
+        }
+    }
+
+    /**
+     * Start a sub object.
+     */
+    public long start(long fieldId) {
+        assertNotCompacted();
+        final int id = (int)fieldId;
+
+        if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_OBJECT) {
+            final long count = fieldId & FIELD_COUNT_MASK;
+            if (count == FIELD_COUNT_SINGLE) {
+                return startObjectImpl(id, false);
+            } else if (count == FIELD_COUNT_REPEATED || count == FIELD_COUNT_PACKED) {
+                return startObjectImpl(id, true);
+            }
+        }
+        throw new IllegalArgumentException("Attempt to call start(long) with "
+                + getFieldIdString(fieldId));
+    }
+
+    /**
+     * End the object started by start() that returned token.
+     */
+    public void end(long token) {
+        endObjectImpl(token, getRepeatedFromToken(token));
+    }
+
     //
     // proto3 type: double
     // java type: double
@@ -240,6 +933,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_DOUBLE);
 
+        writeDoubleImpl(id, val);
+    }
+
+    private void writeDoubleImpl(int id, double val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_FIXED64);
             mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
@@ -253,6 +950,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_DOUBLE);
 
+        writeRepeatedDoubleImpl(id, val);
+    }
+
+    private void writeRepeatedDoubleImpl(int id, double val) {
         writeTag(id, WIRE_TYPE_FIXED64);
         mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
     }
@@ -287,6 +988,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FLOAT);
 
+        writeFloatImpl(id, val);
+    }
+
+    private void writeFloatImpl(int id, float val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_FIXED32);
             mBuffer.writeRawFixed32(Float.floatToIntBits(val));
@@ -300,6 +1005,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FLOAT);
 
+        writeRepeatedFloatImpl(id, val);
+    }
+
+    private void writeRepeatedFloatImpl(int id, float val) {
         writeTag(id, WIRE_TYPE_FIXED32);
         mBuffer.writeRawFixed32(Float.floatToIntBits(val));
     }
@@ -357,6 +1066,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT32);
 
+        writeInt32Impl(id, val);
+    }
+
+    private void writeInt32Impl(int id, int val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_VARINT);
             writeUnsignedVarintFromSignedInt(val);
@@ -374,6 +1087,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT32);
 
+        writeRepeatedInt32Impl(id, val);
+    }
+
+    private void writeRepeatedInt32Impl(int id, int val) {
         writeTag(id, WIRE_TYPE_VARINT);
         writeUnsignedVarintFromSignedInt(val);
     }
@@ -418,6 +1135,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT64);
 
+        writeInt64Impl(id, val);
+    }
+
+    private void writeInt64Impl(int id, long val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_VARINT);
             mBuffer.writeRawVarint64(val);
@@ -431,6 +1152,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT64);
 
+        writeRepeatedInt64Impl(id, val);
+    }
+
+    private void writeRepeatedInt64Impl(int id, long val) {
         writeTag(id, WIRE_TYPE_VARINT);
         mBuffer.writeRawVarint64(val);
     }
@@ -470,6 +1195,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT32);
 
+        writeUInt32Impl(id, val);
+    }
+
+    private void writeUInt32Impl(int id, int val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_VARINT);
             mBuffer.writeRawVarint32(val);
@@ -483,6 +1212,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT32);
 
+        writeRepeatedUInt32Impl(id, val);
+    }
+
+    private void writeRepeatedUInt32Impl(int id, int val) {
         writeTag(id, WIRE_TYPE_VARINT);
         mBuffer.writeRawVarint32(val);
     }
@@ -522,6 +1255,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT64);
 
+        writeUInt64Impl(id, val);
+    }
+
+    private void writeUInt64Impl(int id, long val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_VARINT);
             mBuffer.writeRawVarint64(val);
@@ -535,6 +1272,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT64);
 
+        writeRepeatedUInt64Impl(id, val);
+    }
+
+    private void writeRepeatedUInt64Impl(int id, long val) {
         writeTag(id, WIRE_TYPE_VARINT);
         mBuffer.writeRawVarint64(val);
     }
@@ -574,6 +1315,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT32);
 
+        writeSInt32Impl(id, val);
+    }
+
+    private void writeSInt32Impl(int id, int val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_VARINT);
             mBuffer.writeRawZigZag32(val);
@@ -587,6 +1332,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT32);
 
+        writeRepeatedSInt32Impl(id, val);
+    }
+
+    private void writeRepeatedSInt32Impl(int id, int val) {
         writeTag(id, WIRE_TYPE_VARINT);
         mBuffer.writeRawZigZag32(val);
     }
@@ -626,6 +1375,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT64);
 
+        writeSInt64Impl(id, val);
+    }
+
+    private void writeSInt64Impl(int id, long val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_VARINT);
             mBuffer.writeRawZigZag64(val);
@@ -639,6 +1392,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT64);
 
+        writeRepeatedSInt64Impl(id, val);
+    }
+
+    private void writeRepeatedSInt64Impl(int id, long val) {
         writeTag(id, WIRE_TYPE_VARINT);
         mBuffer.writeRawZigZag64(val);
     }
@@ -677,6 +1434,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED32);
 
+        writeFixed32Impl(id, val);
+    }
+
+    private void writeFixed32Impl(int id, int val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_FIXED32);
             mBuffer.writeRawFixed32(val);
@@ -690,6 +1451,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED32);
 
+        writeRepeatedFixed32Impl(id, val);
+    }
+
+    private void writeRepeatedFixed32Impl(int id, int val) {
         writeTag(id, WIRE_TYPE_FIXED32);
         mBuffer.writeRawFixed32(val);
     }
@@ -724,6 +1489,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED64);
 
+        writeFixed64Impl(id, val);
+    }
+
+    private void writeFixed64Impl(int id, long val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_FIXED64);
             mBuffer.writeRawFixed64(val);
@@ -737,6 +1506,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED64);
 
+        writeRepeatedFixed64(id, val);
+    }
+
+    private void writeRepeatedFixed64Impl(int id, long val) {
         writeTag(id, WIRE_TYPE_FIXED64);
         mBuffer.writeRawFixed64(val);
     }
@@ -770,6 +1543,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED32);
 
+        writeSFixed32Impl(id, val);
+    }
+
+    private void writeSFixed32Impl(int id, int val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_FIXED32);
             mBuffer.writeRawFixed32(val);
@@ -783,6 +1560,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED32);
 
+        writeRepeatedSFixed32Impl(id, val);
+    }
+
+    private void writeRepeatedSFixed32Impl(int id, int val) {
         writeTag(id, WIRE_TYPE_FIXED32);
         mBuffer.writeRawFixed32(val);
     }
@@ -817,6 +1598,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED64);
 
+        writeSFixed64Impl(id, val);
+    }
+
+    private void writeSFixed64Impl(int id, long val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_FIXED64);
             mBuffer.writeRawFixed64(val);
@@ -830,6 +1615,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED64);
 
+        writeRepeatedSFixed64(id, val);
+    }
+
+    private void writeRepeatedSFixed64Impl(int id, long val) {
         writeTag(id, WIRE_TYPE_FIXED64);
         mBuffer.writeRawFixed64(val);
     }
@@ -864,6 +1653,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BOOL);
 
+        writeBoolImpl(id, val);
+    }
+
+    private void writeBoolImpl(int id, boolean val) {
         if (val) {
             writeTag(id, WIRE_TYPE_VARINT);
             // 0 and 1 are the same as their varint counterparts
@@ -878,6 +1671,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BOOL);
 
+        writeRepeatedBool(id, val);
+    }
+
+    private void writeRepeatedBoolImpl(int id, boolean val) {
         writeTag(id, WIRE_TYPE_VARINT);
         mBuffer.writeRawByte((byte)(val ? 1 : 0));
     }
@@ -916,6 +1713,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_STRING);
 
+        writeStringImpl(id, val);
+    }
+
+    private void writeStringImpl(int id, String val) {
         if (val != null && val.length() > 0) {
             writeUtf8String(id, val);
         }
@@ -928,6 +1729,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_STRING);
 
+        writeRepeatedStringImpl(id, val);
+    }
+
+    private void writeRepeatedStringImpl(int id, String val) {
         if (val == null || val.length() == 0) {
             writeKnownLengthHeader(id, 0);
         } else {
@@ -963,6 +1768,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES);
 
+        writeBytesImpl(id, val);
+    }
+
+    private void writeBytesImpl(int id, byte[] val) {
         if (val != null && val.length > 0) {
             writeKnownLengthHeader(id, val.length);
             mBuffer.writeRawBuffer(val);
@@ -976,6 +1785,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BYTES);
 
+        writeRepeatedBytesImpl(id, val);
+    }
+
+    private void writeRepeatedBytesImpl(int id, byte[] val) {
         writeKnownLengthHeader(id, val == null ? 0 : val.length);
         mBuffer.writeRawBuffer(val);
     }
@@ -995,6 +1808,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_ENUM);
 
+        writeEnumImpl(id, val);
+    }
+
+    private void writeEnumImpl(int id, int val) {
         if (val != 0) {
             writeTag(id, WIRE_TYPE_VARINT);
             writeUnsignedVarintFromSignedInt(val);
@@ -1008,6 +1825,10 @@
         assertNotCompacted();
         final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_ENUM);
 
+        writeRepeatedEnumImpl(id, val);
+    }
+
+    private void writeRepeatedEnumImpl(int id, int val) {
         writeTag(id, WIRE_TYPE_VARINT);
         writeUnsignedVarintFromSignedInt(val);
     }
@@ -1239,6 +2060,38 @@
         }
     }
 
+    /**
+     * Write an object that has already been flattend.
+     */
+    public void writeObject(long fieldId, byte[] value) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+
+        writeObjectImpl(id, value);
+    }
+
+    public void writeObjectImpl(int id, byte[] value) {
+        if (value != null && value.length != 0) {
+            writeKnownLengthHeader(id, value.length);
+            mBuffer.writeRawBuffer(value);
+        }
+    }
+
+    /**
+     * Write an object that has already been flattend.
+     */
+    public void writeRepeatedObject(long fieldId, byte[] value) {
+        assertNotCompacted();
+        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+
+        writeRepeatedObjectImpl(id, value);
+    }
+
+    public void writeRepeatedObjectImpl(int id, byte[] value) {
+        writeKnownLengthHeader(id, value == null ? 0 : value.length);
+        mBuffer.writeRawBuffer(value);
+    }
+
     //
     // Tags
     //
@@ -1358,6 +2211,25 @@
         }
     }
 
+    /**
+     * Get a debug string for a fieldId.
+     */
+    private String getFieldIdString(long fieldId) {
+        final long fieldCount = fieldId & FIELD_COUNT_MASK;
+        String countString = getFieldCountString(fieldCount);
+        if (countString == null) {
+            countString = "fieldCount=" + fieldCount;
+        }
+
+        final long fieldType = fieldId & FIELD_TYPE_MASK;
+        String typeString = getFieldTypeString(fieldType);
+        if (typeString == null) {
+            typeString = "fieldType=" + fieldType;
+        }
+
+        return fieldCount + " " + typeString + " tag=" + ((int)fieldId)
+                + " fieldId=0x" + Long.toHexString(fieldId);
+    }
 
     /**
      * Return how many bytes an encoded field tag will require.
@@ -1580,6 +2452,37 @@
     }
 
     /**
+     * Write remaining data to the output stream.  If there is no output stream,
+     * this function does nothing. Any currently open objects (i.e. ones that
+     * have not had endObject called for them will not be written).  Whether this
+     * writes objects that are closed if there are remaining open objects is
+     * undefined (current implementation does not write it, future ones will).
+     * For now, can either call getBytes() or flush(), but not both.
+     */
+    public void flush() {
+        if (mStream == null) {
+            return;
+        }
+        if (mDepth != 0) {
+            // TODO: The compacting code isn't ready yet to compact unless we're done.
+            // TODO: Fix that.
+            return;
+        }
+        if (mCompacted) {
+            // If we're compacted, we already wrote it finished.
+            return;
+        }
+        compactIfNecessary();
+        final byte[] data = mBuffer.getBytes(mBuffer.getReadableSize());
+        try {
+            mStream.write(data);
+            mStream.flush();
+        } catch (IOException ex) {
+            throw new RuntimeException("Error flushing proto to stream", ex);
+        }
+    }
+
+    /**
      * Read a raw tag from the buffer.
      */
     private int readRawTag() {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index fc1520b..5fa1d2b 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -1835,7 +1835,8 @@
         // can expect the OnAttachStateChangeListener to have been called prior
         // to executing this method, so we can rely on that instead.
         final Transition exitTransition = mExitTransition;
-        if (mIsAnchorRootAttached && exitTransition != null && decorView.isLaidOut()) {
+        if (exitTransition != null && decorView.isLaidOut()
+                && (mIsAnchorRootAttached || mAnchorRoot == null)) {
             // The decor view is non-interactive and non-IME-focusable during exit transitions.
             final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
             p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -1843,18 +1844,13 @@
             p.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
             mWindowManager.updateViewLayout(decorView, p);
 
+            final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
+            final Rect epicenter = getTransitionEpicenter();
+
             // Once we start dismissing the decor view, all state (including
             // the anchor root) needs to be moved to the decor view since we
             // may open another popup while it's busy exiting.
-            final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
-            final Rect epicenter = getTransitionEpicenter();
-            exitTransition.setEpicenterCallback(new EpicenterCallback() {
-                @Override
-                public Rect onGetEpicenter(Transition transition) {
-                    return epicenter;
-                }
-            });
-            decorView.startExitTransition(exitTransition, anchorRoot,
+            decorView.startExitTransition(exitTransition, anchorRoot, epicenter,
                     new TransitionListenerAdapter() {
                         @Override
                         public void onTransitionEnd(Transition transition) {
@@ -2349,8 +2345,9 @@
          * its {@code onTransitionEnd} method called even if the transition
          * never starts; however, it may be called with a {@code null} argument.
          */
-        public void startExitTransition(Transition transition, final View anchorRoot,
-                final TransitionListener listener) {
+        public void startExitTransition(@NonNull Transition transition,
+                @Nullable final View anchorRoot, @Nullable final Rect epicenter,
+                @NonNull final TransitionListener listener) {
             if (transition == null) {
                 return;
             }
@@ -2358,24 +2355,35 @@
             // The anchor view's window may go away while we're executing our
             // transition, in which case we need to end the transition
             // immediately and execute the listener to remove the popup.
-            anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+            if (anchorRoot != null) {
+                anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+            }
 
             // The exit listener MUST be called for cleanup, even if the
             // transition never starts or ends. Stash it for later.
             mPendingExitListener = new TransitionListenerAdapter() {
                 @Override
-                public void onTransitionEnd(Transition transition) {
-                    anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
-                    listener.onTransitionEnd(transition);
+                public void onTransitionEnd(Transition t) {
+                    if (anchorRoot != null) {
+                        anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+                    }
+
+                    listener.onTransitionEnd(t);
 
                     // The listener was called. Our job here is done.
                     mPendingExitListener = null;
-                    transition.removeListener(this);
+                    t.removeListener(this);
                 }
             };
 
             final Transition exitTransition = transition.clone();
             exitTransition.addListener(mPendingExitListener);
+            exitTransition.setEpicenterCallback(new EpicenterCallback() {
+                @Override
+                public Rect onGetEpicenter(Transition transition) {
+                    return epicenter;
+                }
+            });
 
             final int count = getChildCount();
             for (int i = 0; i < count; i++) {
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 0bf170e..af4f777 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -20,28 +20,22 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.widget.ActionMenuView;
 import android.widget.ForwardingListener;
 import android.widget.TextView;
-import android.widget.Toast;
 
 /**
  * @hide
  */
 public class ActionMenuItemView extends TextView
-        implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
-        ActionMenuView.ActionMenuChildView {
+        implements MenuView.ItemView, View.OnClickListener, ActionMenuView.ActionMenuChildView {
     private static final String TAG = "ActionMenuItemView";
 
     private MenuItemImpl mItemData;
@@ -59,9 +53,6 @@
     private static final int MAX_ICON_SIZE = 32; // dp
     private int mMaxIconSize;
 
-    private Toast mTooltip;
-    private Runnable mShowTooltipRunnable = () -> showTooltip(Toast.LENGTH_LONG);
-
     public ActionMenuItemView(Context context) {
         this(context, null);
     }
@@ -88,7 +79,6 @@
         mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
 
         setOnClickListener(this);
-        setOnLongClickListener(this);
 
         mSavedPaddingLeft = -1;
         setSaveEnabled(false);
@@ -193,6 +183,9 @@
                 (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
 
         setText(visible ? mTitle : null);
+
+        // Show the tooltip for items that do not already show text.
+        setTooltip(visible ? null : mTitle);
     }
 
     public void setIcon(Drawable icon) {
@@ -249,7 +242,6 @@
 
     @Override
     public boolean dispatchHoverEvent(MotionEvent event) {
-        updateTooltip(event);
         // Don't allow children to hover; we want this to be treated as a single component.
         return onHoverEvent(event);
     }
@@ -267,68 +259,6 @@
     }
 
     @Override
-    public boolean onLongClick(View v) {
-        return showTooltip(Toast.LENGTH_SHORT);
-    }
-
-    private boolean showTooltip(@Toast.Duration int duration) {
-        if (hasText()) {
-            // Don't show the cheat sheet for items that already show text.
-            return false;
-        }
-
-        final int[] screenPos = new int[2];
-        final Rect displayFrame = new Rect();
-        getLocationOnScreen(screenPos);
-        getWindowVisibleDisplayFrame(displayFrame);
-
-        final Context context = getContext();
-        final int width = getWidth();
-        final int height = getHeight();
-        final int midy = screenPos[1] + height / 2;
-        int referenceX = screenPos[0] + width / 2;
-        if (getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) {
-            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-            referenceX = screenWidth - referenceX; // mirror
-        }
-        hideTooltip ();
-        mTooltip = Toast.makeText(context, mItemData.getTitle(), duration);
-        if (midy < displayFrame.height()) {
-            // Show along the top; follow action buttons
-            mTooltip.setGravity(Gravity.TOP | Gravity.END, referenceX,
-                    screenPos[1] + height - displayFrame.top);
-        } else {
-            // Show along the bottom center
-            mTooltip.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
-        }
-        mTooltip.show();
-        return true;
-    }
-
-    private void hideTooltip() {
-        if (mTooltip != null) {
-            mTooltip.cancel();
-            mTooltip = null;
-        }
-        getHandler().removeCallbacks(mShowTooltipRunnable);
-    }
-
-    private void updateTooltip(MotionEvent event) {
-        AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
-        if (manager.isEnabled() && manager.isTouchExplorationEnabled()) {
-            return;
-        }
-
-        final int action = event.getAction();
-        if (action == MotionEvent.ACTION_HOVER_MOVE) {
-            hideTooltip();
-            getHandler().postDelayed(mShowTooltipRunnable, ViewConfiguration.getLongPressTimeout());
-        } else if (action == MotionEvent.ACTION_HOVER_EXIT) {
-            hideTooltip();
-        }
-    }
-
-    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final boolean textVisible = hasText();
         if (textVisible && mSavedPaddingLeft >= 0) {
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 358be60..a5d2bf3 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -71,10 +71,14 @@
                 .setTextDirection(getTextDirectionHeuristic())
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                 .setIncludePad(getIncludeFontPadding())
-                .setEllipsize(shouldEllipsize ? effectiveEllipsize : null)
-                .setEllipsizedWidth(ellipsisWidth)
                 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
-                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
+                .setMaxLines(getMaxLines() >= 0 ? getMaxLines() : Integer.MAX_VALUE);
+        if (shouldEllipsize) {
+            builder.setEllipsize(effectiveEllipsize)
+                    .setEllipsizedWidth(ellipsisWidth);
+        }
+
         // we set the endmargin on the requested number of lines.
         int endMargin = getContext().getResources().getDimensionPixelSize(
                 R.dimen.notification_content_picture_margin);
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index d2a43b7..cb123a1 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -126,7 +126,8 @@
                 // Pretend we need the image padding for all views, we don't know which
                 // one will end up needing to do this (might end up not using all the space,
                 // but calculating this exactly would be more expensive).
-                ((ImageFloatingTextView) child).setNumIndentLines(mIndentLines);
+                ((ImageFloatingTextView) child).setNumIndentLines(
+                        mIndentLines == 2 ? 3 : mIndentLines);
             }
 
             measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
@@ -147,6 +148,9 @@
         // Now that we know which views to take, fix up the indents and see what width we get.
         int measuredWidth = mPaddingLeft + mPaddingRight;
         int imageLines = mIndentLines;
+        // Need to redo the height because it may change due to changing indents.
+        totalHeight = mPaddingTop + mPaddingBottom;
+        first = true;
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
@@ -173,6 +177,9 @@
             measuredWidth = Math.max(measuredWidth,
                     child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
                             + mPaddingLeft + mPaddingRight);
+            totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
+                    lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
+            first = false;
         }
 
 
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index ffd9b24..4466575 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -40,7 +40,6 @@
 import android.widget.ListView;
 import android.widget.Spinner;
 import android.widget.TextView;
-import android.widget.Toast;
 
 /**
  * This widget implements the dynamic action bar tab behavior that can change
@@ -360,7 +359,7 @@
         tabView.getTab().select();
     }
 
-    private class TabView extends LinearLayout implements OnLongClickListener {
+    private class TabView extends LinearLayout {
         private ActionBar.Tab mTab;
         private TextView mTextView;
         private ImageView mIconView;
@@ -472,35 +471,10 @@
                 if (mIconView != null) {
                     mIconView.setContentDescription(tab.getContentDescription());
                 }
-
-                if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
-                    setOnLongClickListener(this);
-                } else {
-                    setOnLongClickListener(null);
-                    setLongClickable(false);
-                }
+                setTooltip(hasText? null : tab.getContentDescription());
             }
         }
 
-        public boolean onLongClick(View v) {
-            final int[] screenPos = new int[2];
-            getLocationOnScreen(screenPos);
-
-            final Context context = getContext();
-            final int width = getWidth();
-            final int height = getHeight();
-            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-
-            Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
-                    Toast.LENGTH_SHORT);
-            // Show under the tab
-            cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
-                    (screenPos[0] + width / 2) - screenWidth / 2, height);
-
-            cheatSheet.show();
-            return true;
-        }
-
         public ActionBar.Tab getTab() {
             return mTab;
         }
diff --git a/core/jni/android_util_MemoryIntArray.cpp b/core/jni/android_util_MemoryIntArray.cpp
index 9513c8b..2dfbe3e 100644
--- a/core/jni/android_util_MemoryIntArray.cpp
+++ b/core/jni/android_util_MemoryIntArray.cpp
@@ -54,7 +54,7 @@
 }
 
 static jlong android_util_MemoryIntArray_open(JNIEnv* env, jobject clazz, jint fd,
-    jboolean owner, jboolean writable)
+    jboolean owner)
 {
     if (fd < 0) {
         jniThrowException(env, "java/io/IOException", "bad file descriptor");
@@ -72,19 +72,35 @@
         return -1;
     }
 
-    int protMode = (owner || writable) ? (PROT_READ | PROT_WRITE) : PROT_READ;
+    // IMPORTANT: Ashmem allows the caller to change its size until
+    // it is memory mapped for the first time which lazily creates
+    // the underlying VFS file. So the size we get above may not
+    // reflect the size of the underlying shared memory region. Therefore,
+    // we first memory map to set the size in stone an verify if
+    // the underlying ashmem region has the same size as the one we
+    // memory mapped. This is critical as we use the underlying
+    // ashmem size for boundary checks and memory unmapping.
+    int protMode = owner ? (PROT_READ | PROT_WRITE) : PROT_READ;
     void* ashmemAddr = mmap(NULL, ashmemSize, protMode, MAP_SHARED, fd, 0);
     if (ashmemAddr == MAP_FAILED) {
         jniThrowException(env, "java/io/IOException", "cannot mmap ashmem");
         return -1;
     }
 
+    // Check if the mapped size is the same as the ashmem region.
+    int mmapedSize = ashmem_get_size_region(fd);
+    if (mmapedSize != ashmemSize) {
+        munmap(reinterpret_cast<void *>(ashmemAddr), ashmemSize);
+        jniThrowException(env, "java/io/IOException", "bad file descriptor");
+        return -1;
+    }
+
     if (owner) {
         int size = ashmemSize / sizeof(std::atomic_int);
         new (ashmemAddr) std::atomic_int[size];
     }
 
-    if (owner && !writable) {
+    if (owner) {
         int setProtResult = ashmem_set_prot_region(fd, PROT_READ);
         if (setProtResult < 0) {
             jniThrowException(env, "java/io/IOException", "cannot set ashmem prot mode");
@@ -131,7 +147,7 @@
 }
 
 static jint android_util_MemoryIntArray_get(JNIEnv* env, jobject clazz,
-        jint fd, jlong address, jint index, jboolean owner)
+        jint fd, jlong address, jint index)
 {
     if (fd < 0) {
         jniThrowException(env, "java/io/IOException", "bad file descriptor");
@@ -153,7 +169,7 @@
 }
 
 static void android_util_MemoryIntArray_set(JNIEnv* env, jobject clazz,
-        jint fd, jlong address, jint index, jint newValue, jboolean owner)
+        jint fd, jlong address, jint index, jint newValue)
 {
     if (fd < 0) {
         jniThrowException(env, "java/io/IOException", "bad file descriptor");
@@ -195,10 +211,10 @@
 
 static const JNINativeMethod methods[] = {
     {"nativeCreate",  "(Ljava/lang/String;I)I", (void*)android_util_MemoryIntArray_create},
-    {"nativeOpen",  "(IZZ)J", (void*)android_util_MemoryIntArray_open},
+    {"nativeOpen",  "(IZ)J", (void*)android_util_MemoryIntArray_open},
     {"nativeClose", "(IJZ)V", (void*)android_util_MemoryIntArray_close},
-    {"nativeGet",  "(IJIZ)I", (void*)android_util_MemoryIntArray_get},
-    {"nativeSet", "(IJIIZ)V", (void*) android_util_MemoryIntArray_set},
+    {"nativeGet",  "(IJI)I", (void*)android_util_MemoryIntArray_get},
+    {"nativeSet", "(IJII)V", (void*) android_util_MemoryIntArray_set},
     {"nativeSize", "(I)I", (void*) android_util_MemoryIntArray_size},
 };
 
diff --git a/core/tests/coretests/res/layout/messaging_linear_layout_test.xml b/core/tests/coretests/res/layout/messaging_linear_layout_test.xml
new file mode 100644
index 0000000..8ba3e07
--- /dev/null
+++ b/core/tests/coretests/res/layout/messaging_linear_layout_test.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 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.
+  -->
+
+<com.android.internal.widget.MessagingLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:maxHeight="300px"
+    android:spacing="5px">
+
+</com.android.internal.widget.MessagingLinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/widget/ImageFloatingTextViewTest.java b/core/tests/coretests/src/com/android/internal/widget/ImageFloatingTextViewTest.java
new file mode 100644
index 0000000..5dc07c2
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/ImageFloatingTextViewTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 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.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.view.View.MeasureSpec;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class ImageFloatingTextViewTest {
+
+    private Context mContext;
+    private ImageFloatingTextView mView;
+    private TextView mTextView;
+
+    @Before
+    public void setup() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mView = new ImageFloatingTextView(mContext, null, 0, 0);
+        mTextView = new TextView(mContext, null, 0, 0);
+        mTextView.setMaxLines(9);
+    }
+
+    @Test
+    public void testEmpty() {
+        parametrizedTest("");
+    }
+
+    @Test
+    public void testSingleLine() {
+        parametrizedTest("Hello, World!");
+    }
+
+    @Test
+    public void testTwoLine() {
+        parametrizedTest("Hello, World!\nWhat a nice day!");
+    }
+
+    @Test
+    public void testShort() {
+        parametrizedTest("Hello, World! What a nice day! Let's try some more text. "
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet.");
+    }
+
+    @Test
+    public void testLong() {
+        parametrizedTest("Hello, World! What a nice day! Let's try some more text. "
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+                + "Yada yada, yada yada. Lorem ipsum dolor sit amet.");
+    }
+
+    private void parametrizedTest(CharSequence text) {
+        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.AT_MOST);
+        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
+
+        mTextView.setText(text);
+        mView.setText(text);
+
+        mTextView.measure(widthMeasureSpec, heightMeasureSpec);
+        mView.measure(widthMeasureSpec, heightMeasureSpec);
+
+        assertEquals(mTextView.getMeasuredHeight(), mView.getMeasuredHeight());
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
new file mode 100644
index 0000000..75b2c1d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2016 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.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.Debug;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.core.deps.guava.base.Function;
+import android.support.test.filters.SmallTest;
+import android.view.LayoutInflater;
+import android.view.View.MeasureSpec;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+@SmallTest
+public class MessagingLinearLayoutTest {
+
+    public static final int WIDTH_SPEC = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
+    public static final int HEIGHT_SPEC = MeasureSpec.makeMeasureSpec(400, MeasureSpec.AT_MOST);
+    private Context mContext;
+    private MessagingLinearLayout mView;
+
+    @Before
+    public void setup() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        // maxHeight: 300px
+        // spacing: 50px
+        mView = (MessagingLinearLayout) LayoutInflater.from(mContext).inflate(
+                R.layout.messaging_linear_layout_test, null);
+    }
+
+    @Test
+    public void testSingleChild() {
+        FakeImageFloatingTextView child = fakeChild((i) -> 3);
+
+        mView.setNumIndentLines(2);
+        mView.addView(child);
+
+        mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+        assertEquals(3, child.getNumIndentLines());
+        assertFalse(child.isHidden());
+        assertEquals(150, mView.getMeasuredHeight());
+    }
+
+    @Test
+    public void testLargeSmall() {
+        FakeImageFloatingTextView child1 = fakeChild((i) -> 3);
+        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+
+        mView.setNumIndentLines(2);
+        mView.addView(child1);
+        mView.addView(child2);
+
+        mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+        assertEquals(3, child1.getNumIndentLines());
+        assertEquals(0, child2.getNumIndentLines());
+        assertFalse(child1.isHidden());
+        assertFalse(child2.isHidden());
+        assertEquals(205, mView.getMeasuredHeight());
+    }
+
+    @Test
+    public void testSmallSmall() {
+        FakeImageFloatingTextView child1 = fakeChild((i) -> 1);
+        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+
+        mView.setNumIndentLines(2);
+        mView.addView(child1);
+        mView.addView(child2);
+
+        mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+        assertEquals(2, child1.getNumIndentLines());
+        assertEquals(1, child2.getNumIndentLines());
+        assertFalse(child1.isHidden());
+        assertFalse(child2.isHidden());
+        assertEquals(105, mView.getMeasuredHeight());
+    }
+
+    @Test
+    public void testLargeLarge() {
+        FakeImageFloatingTextView child1 = fakeChild((i) -> 7);
+        FakeImageFloatingTextView child2 = fakeChild((i) -> 7);
+
+        mView.setNumIndentLines(2);
+        mView.addView(child1);
+        mView.addView(child2);
+
+        mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+        assertEquals(3, child2.getNumIndentLines());
+        assertTrue(child1.isHidden());
+        assertFalse(child2.isHidden());
+        assertEquals(300, mView.getMeasuredHeight());
+    }
+
+    @Test
+    public void testLargeSmall_largeWrapsWith3indentbutnot3_andHitsMax() {
+        FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 5 : 4);
+        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+
+        mView.setNumIndentLines(2);
+        mView.addView(child1);
+        mView.addView(child2);
+
+        mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+        assertTrue(child1.isHidden());
+        assertFalse(child2.isHidden());
+        assertEquals(50, mView.getMeasuredHeight());
+        assertEquals(2, child2.getNumIndentLines());
+    }
+
+    @Test
+    public void testLargeSmall_largeWrapsWith3indentbutnot3() {
+        FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 4 : 3);
+        FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+
+        mView.setNumIndentLines(2);
+        mView.addView(child1);
+        mView.addView(child2);
+
+        mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+        assertFalse(child1.isHidden());
+        assertFalse(child2.isHidden());
+        assertEquals(255, mView.getMeasuredHeight());
+        assertEquals(3, child1.getNumIndentLines());
+        assertEquals(0, child2.getNumIndentLines());
+    }
+
+    private class FakeImageFloatingTextView extends ImageFloatingTextView {
+
+        public static final int LINE_HEIGHT = 50;
+        private final Function<Integer, Integer> mLinesForIndent;
+        private int mNumIndentLines;
+
+        public FakeImageFloatingTextView(Context context,
+                Function<Integer, Integer> linesForIndent) {
+            super(context, null, 0, 0);
+            mLinesForIndent = linesForIndent;
+        }
+
+        @Override
+        public boolean setNumIndentLines(int lines) {
+            boolean changed = (mNumIndentLines != lines);
+            mNumIndentLines = lines;
+            return changed;
+        }
+
+        public int getNumIndentLines() {
+            return mNumIndentLines;
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            setMeasuredDimension(
+                    getDefaultSize(500, widthMeasureSpec),
+                    resolveSize(getDesiredHeight(), heightMeasureSpec));
+        }
+
+        @Override
+        public int getLineCount() {
+            return mLinesForIndent.apply(mNumIndentLines);
+        }
+
+        public int getDesiredHeight() {
+            return LINE_HEIGHT * getLineCount();
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            // swallow
+        }
+
+        public boolean isHidden() {
+            MessagingLinearLayout.LayoutParams lp =
+                    (MessagingLinearLayout.LayoutParams) getLayoutParams();
+            try {
+                Field hide = MessagingLinearLayout.LayoutParams.class.getDeclaredField("hide");
+                hide.setAccessible(true);
+                return hide.getBoolean(lp);
+            } catch (ReflectiveOperationException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private FakeImageFloatingTextView fakeChild(Function<Integer,Integer> linesForIndent) {
+        return new FakeImageFloatingTextView(mContext, linesForIndent);
+    }
+}
diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk
index 6e415f4..46a0d9b 100644
--- a/core/tests/utiltests/Android.mk
+++ b/core/tests/utiltests/Android.mk
@@ -12,6 +12,8 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += src/android/util/IRemoteMemoryIntArray.aidl
 
+LOCAL_JNI_SHARED_LIBRARIES := libmemoryintarraytest libcutils libc++
+
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     frameworks-base-testutils \
diff --git a/core/tests/utiltests/jni/Android.bp b/core/tests/utiltests/jni/Android.bp
new file mode 100644
index 0000000..e9a4144
--- /dev/null
+++ b/core/tests/utiltests/jni/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2016 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.

+

+cc_library_shared {

+    name: "libmemoryintarraytest",

+    shared_libs: [

+        "libcutils",

+    ],

+    clang: true,

+    stl: "libc++",

+    srcs: [

+        "registration.cpp",

+        "android_util_MemoryIntArrayTest.cpp",

+    ],

+    cflags: ["-Werror"],

+}
\ No newline at end of file
diff --git a/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp b/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp
new file mode 100644
index 0000000..57ee2d5
--- /dev/null
+++ b/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 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 <atomic>
+#include <jni.h>
+#include <cutils/ashmem.h>
+#include <linux/ashmem.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+jint android_util_MemoryIntArrayTest_createAshmem(__attribute__((unused)) JNIEnv* env,
+        __attribute__((unused)) jobject clazz,
+        jstring name, jint size)
+{
+
+    if (name == NULL) {
+        return -1;
+    }
+
+    if (size < 0) {
+        return -1;
+    }
+
+    const char* nameStr = env->GetStringUTFChars(name, NULL);
+    const int ashmemSize = sizeof(std::atomic_int) * size;
+    int fd = ashmem_create_region(nameStr, ashmemSize);
+    env->ReleaseStringUTFChars(name, nameStr);
+
+    if (fd < 0) {
+        return -1;
+    }
+
+    int setProtResult = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
+    if (setProtResult < 0) {
+        return -1;
+    }
+
+    return fd;
+}
+
+void android_util_MemoryIntArrayTest_setAshmemSize(__attribute__((unused)) JNIEnv* env,
+        __attribute__((unused)) jobject clazz, jint fd, jint size)
+{
+    if (fd < 0) {
+        return;
+    }
+
+    if (size < 0) {
+        return;
+    }
+
+    ioctl(fd, ASHMEM_SET_SIZE, size);
+}
diff --git a/core/tests/utiltests/jni/registration.cpp b/core/tests/utiltests/jni/registration.cpp
new file mode 100644
index 0000000..0c84d98
--- /dev/null
+++ b/core/tests/utiltests/jni/registration.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 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 <jni.h>
+
+extern jint android_util_MemoryIntArrayTest_createAshmem(JNIEnv* env,
+        jobject clazz, jstring name, jint size);
+extern void android_util_MemoryIntArrayTest_setAshmemSize(JNIEnv* env,
+       jobject clazz, jint fd, jint size);
+
+extern "C" {
+    JNIEXPORT jint JNICALL Java_android_util_MemoryIntArrayTest_nativeCreateAshmem(
+            JNIEnv * env, jobject obj, jstring name, jint size);
+    JNIEXPORT void JNICALL Java_android_util_MemoryIntArrayTest_nativeSetAshmemSize(
+            JNIEnv * env, jobject obj, jint fd, jint size);
+};
+
+JNIEXPORT jint JNICALL Java_android_util_MemoryIntArrayTest_nativeCreateAshmem(
+        __attribute__((unused)) JNIEnv * env,__attribute__((unused)) jobject obj,
+        jstring name, jint size)
+{
+    return android_util_MemoryIntArrayTest_createAshmem(env, obj, name, size);
+}
+
+JNIEXPORT void JNICALL Java_android_util_MemoryIntArrayTest_nativeSetAshmemSize(
+        __attribute__((unused)) JNIEnv * env,__attribute__((unused)) jobject obj,
+        jint fd, jint size)
+{
+    android_util_MemoryIntArrayTest_setAshmemSize(env, obj, fd, size);
+}
diff --git a/core/tests/utiltests/src/android/util/IRemoteMemoryIntArray.aidl b/core/tests/utiltests/src/android/util/IRemoteMemoryIntArray.aidl
index 0a65fff2..10d14f1 100644
--- a/core/tests/utiltests/src/android/util/IRemoteMemoryIntArray.aidl
+++ b/core/tests/utiltests/src/android/util/IRemoteMemoryIntArray.aidl
@@ -20,11 +20,12 @@
 
 interface IRemoteMemoryIntArray {
     MemoryIntArray peekInstance();
-    void create(int size, boolean clientWritable);
+    void create(int size);
     boolean isWritable();
     int get(int index);
     void set(int index, int value);
     int size();
     void close();
     boolean isClosed();
+    void accessLastElementInRemoteProcess(in MemoryIntArray array);
 }
diff --git a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
index 129e6b7..85817bb 100644
--- a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
+++ b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
@@ -28,14 +28,22 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.reflect.Field;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @RunWith(AndroidJUnit4.class)
 public class MemoryIntArrayTest {
+    static {
+        System.loadLibrary("cutils");
+        System.loadLibrary("memoryintarraytest");
+    }
 
     @Test
     public void testSize() throws Exception {
         MemoryIntArray array = null;
         try {
-            array = new MemoryIntArray(3, false);
+            array = new MemoryIntArray(3);
             assertEquals("size must be three", 3, array.size());
         } finally {
             IoUtils.closeQuietly(array);
@@ -46,7 +54,7 @@
     public void testGetSet() throws Exception {
         MemoryIntArray array = null;
         try {
-            array = new MemoryIntArray(3, false);
+            array = new MemoryIntArray(3);
 
             array.set(0, 1);
             array.set(1, 2);
@@ -64,7 +72,7 @@
     public void testWritable() throws Exception {
         MemoryIntArray array = null;
         try {
-            array = new MemoryIntArray(3, true);
+            array = new MemoryIntArray(3);
             assertTrue("Must be mutable", array.isWritable());
         } finally {
             IoUtils.closeQuietly(array);
@@ -75,7 +83,7 @@
     public void testClose() throws Exception {
         MemoryIntArray array = null;
         try {
-            array = new MemoryIntArray(3, false);
+            array = new MemoryIntArray(3);
             array.close();
             assertTrue("Must be closed", array.isClosed());
         } finally {
@@ -90,7 +98,7 @@
         MemoryIntArray firstArray = null;
         MemoryIntArray secondArray = null;
         try {
-            firstArray = new MemoryIntArray(3, false);
+            firstArray = new MemoryIntArray(3);
 
             firstArray.set(0, 1);
             firstArray.set(1, 2);
@@ -117,7 +125,7 @@
     public void testInteractOnceClosed() throws Exception {
         MemoryIntArray array = null;
         try {
-            array = new MemoryIntArray(3, false);
+            array = new MemoryIntArray(3);
             array.close();
 
             array.close();
@@ -160,7 +168,7 @@
     public void testInteractPutOfBounds() throws Exception {
         MemoryIntArray array = null;
         try {
-            array = new MemoryIntArray(3, false);
+            array = new MemoryIntArray(3);
 
             try {
                 array.get(-1);
@@ -198,7 +206,7 @@
     public void testOverMaxSize() throws Exception {
         MemoryIntArray array = null;
         try {
-            array = new MemoryIntArray(MemoryIntArray.getMaxSize() + 1, false);
+            array = new MemoryIntArray(MemoryIntArray.getMaxSize() + 1);
             fail("Cannot use over max size");
         } catch (IllegalArgumentException e) {
             /* expected */
@@ -209,7 +217,7 @@
 
     @Test
     public void testNotMutableByUnprivilegedClients() throws Exception {
-        RemoteIntArray remoteIntArray = new RemoteIntArray(1, false);
+        RemoteIntArray remoteIntArray = new RemoteIntArray(1);
         try {
             assertNotNull("Couldn't get remote instance", remoteIntArray);
             MemoryIntArray localIntArray = remoteIntArray.peekInstance();
@@ -230,4 +238,64 @@
             remoteIntArray.destroy();
         }
     }
+
+    @Test
+    public void testAshmemSizeMatchesMemoryIntArraySize() throws Exception {
+        boolean success = false;
+
+        // Get a handle to a remote process to send the fd
+        RemoteIntArray remoteIntArray = new RemoteIntArray(1);
+        try {
+            // Let us try 100 times
+            for (int i = 0; i < 100; i++) {
+                // Create a MemoryIntArray to muck with
+                MemoryIntArray array = new MemoryIntArray(1);
+
+                // Create the fd to stuff in the MemoryIntArray
+                final int fd = nativeCreateAshmem("foo", 1);
+
+                // Replace the fd with our ahsmem region
+                Field fdFiled = MemoryIntArray.class.getDeclaredField("mFd");
+                fdFiled.setAccessible(true);
+                fdFiled.set(array, fd);
+
+                CountDownLatch countDownLatch = new CountDownLatch(2);
+
+                new Thread() {
+                    @Override
+                    public void run() {
+                        for (int i = 2; i < Integer.MAX_VALUE; i++) {
+                            if (countDownLatch.getCount() == 1) {
+                                countDownLatch.countDown();
+                                return;
+                            }
+                            nativeSetAshmemSize(fd, i);
+                        }
+                    }
+                }.start();
+
+                try {
+                    remoteIntArray.accessLastElementInRemoteProcess(array);
+                } catch (IllegalArgumentException e) {
+                    success = true;
+                }
+
+                countDownLatch.countDown();
+                countDownLatch.await(1000, TimeUnit.MILLISECONDS);
+
+                if (success) {
+                    break;
+                }
+            }
+        } finally {
+            remoteIntArray.destroy();
+        }
+
+        if (!success) {
+            fail("MemoryIntArray should catch ahshmem size changing under it");
+        }
+    }
+
+    private native int nativeCreateAshmem(String name, int size);
+    private native void nativeSetAshmemSize(int fd, int size);
 }
diff --git a/core/tests/utiltests/src/android/util/RemoteIntArray.java b/core/tests/utiltests/src/android/util/RemoteIntArray.java
index 10c325f..7dc3400 100644
--- a/core/tests/utiltests/src/android/util/RemoteIntArray.java
+++ b/core/tests/utiltests/src/android/util/RemoteIntArray.java
@@ -40,7 +40,7 @@
 
     private android.util.IRemoteMemoryIntArray mRemoteInstance;
 
-    public RemoteIntArray(int size, boolean clientWritable) throws IOException, TimeoutException {
+    public RemoteIntArray(int size) throws IOException, TimeoutException {
         mIntent.setComponent(new ComponentName(InstrumentationRegistry.getContext(),
                 RemoteMemoryIntArrayService.class));
         synchronized (mLock) {
@@ -48,7 +48,7 @@
                 bindLocked();
             }
             try {
-                mRemoteInstance.create(size, clientWritable);
+                mRemoteInstance.create(size);
             } catch (RemoteException e) {
                 throw new IOException(e);
             }
@@ -148,6 +148,14 @@
         }
     }
 
+    public void accessLastElementInRemoteProcess(MemoryIntArray array) {
+        try {
+            mRemoteInstance.accessLastElementInRemoteProcess(array);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     @Override
     public void onServiceConnected(ComponentName name, IBinder service) {
         synchronized (mLock) {
diff --git a/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java b/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java
index 35ae9a7..9264c6c 100644
--- a/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java
+++ b/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java
@@ -35,10 +35,10 @@
         return new android.util.IRemoteMemoryIntArray.Stub() {
 
             @Override
-            public void create(int size, boolean clientWritable) {
+            public void create(int size) {
                 synchronized (mLock) {
                     try {
-                        mArray = new MemoryIntArray(size, clientWritable);
+                        mArray = new MemoryIntArray(size);
                     } catch (IOException e) {
                         throw new IllegalStateException(e);
                     }
@@ -109,6 +109,15 @@
                     return mArray.isClosed();
                 }
             }
+
+            @Override
+            public void accessLastElementInRemoteProcess(MemoryIntArray array) {
+                try {
+                    array.get(array.size() - 1);
+                } catch (IOException e) {
+                    throw new IllegalStateException(e);
+                }
+            }
         };
     }
 }
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index bb8eb2c..03f0b4d 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -55,12 +55,14 @@
 import java.util.Random;
 
 public class CaptivePortalLoginActivity extends Activity {
-    private static final String TAG = "CaptivePortalLogin";
+    private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName();
+    private static final boolean DBG = true;
+
     private static final int SOCKET_TIMEOUT_MS = 10000;
 
     private enum Result { DISMISSED, UNWANTED, WANTED_AS_IS };
 
-    private URL mURL;
+    private URL mUrl;
     private Network mNetwork;
     private CaptivePortal mCaptivePortal;
     private NetworkCallback mNetworkCallback;
@@ -72,17 +74,18 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mCm = ConnectivityManager.from(this);
-        String url = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
-        if (url == null) url = mCm.getCaptivePortalServerUrl();
-        try {
-            mURL = new URL(url);
-        } catch (MalformedURLException e) {
-            // System misconfigured, bail out in a way that at least provides network access.
-            Log.e(TAG, "Invalid captive portal URL, url=" + url);
-            done(Result.WANTED_AS_IS);
-        }
         mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
         mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
+        mUrl = getUrl();
+        if (mUrl == null) {
+            // getUrl() failed to parse the url provided in the intent: bail out in a way that
+            // at least provides network access.
+            done(Result.WANTED_AS_IS);
+            return;
+        }
+        if (DBG) {
+            Log.d(TAG, String.format("onCreate for %s", mUrl.toString()));
+        }
 
         // Also initializes proxy system properties.
         mCm.bindProcessToNetwork(mNetwork);
@@ -149,6 +152,9 @@
     }
 
     private void done(Result result) {
+        if (DBG) {
+            Log.d(TAG, String.format("Result %s for %s", result.name(), mUrl.toString()));
+        }
         if (mNetworkCallback != null) {
             mCm.unregisterNetworkCallback(mNetworkCallback);
             mNetworkCallback = null;
@@ -185,22 +191,31 @@
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-        int id = item.getItemId();
-        if (id == R.id.action_use_network) {
-            done(Result.WANTED_AS_IS);
-            return true;
+        final Result result;
+        final String action;
+        final int id = item.getItemId();
+        switch (id) {
+            case R.id.action_use_network:
+                result = Result.WANTED_AS_IS;
+                action = "USE_NETWORK";
+                break;
+            case R.id.action_do_not_use_network:
+                result = Result.UNWANTED;
+                action = "DO_NOT_USE_NETWORK";
+                break;
+            default:
+                return super.onOptionsItemSelected(item);
         }
-        if (id == R.id.action_do_not_use_network) {
-            done(Result.UNWANTED);
-            return true;
+        if (DBG) {
+            Log.d(TAG, String.format("onOptionsItemSelect %s for %s", action, mUrl.toString()));
         }
-        return super.onOptionsItemSelected(item);
+        done(result);
+        return true;
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
-
         if (mNetworkCallback != null) {
             mCm.unregisterNetworkCallback(mNetworkCallback);
             mNetworkCallback = null;
@@ -215,10 +230,27 @@
                 } catch (InterruptedException e) {
                 }
             }
-            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(mURL.toString())));
+            final String url = mUrl.toString();
+            if (DBG) {
+                Log.d(TAG, "starting activity with intent ACTION_VIEW for " + url);
+            }
+            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
         }
     }
 
+    private URL getUrl() {
+        String url = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
+        if (url == null) {
+            url = mCm.getCaptivePortalServerUrl();
+        }
+        try {
+            return new URL(url);
+        } catch (MalformedURLException e) {
+            Log.e(TAG, "Invalid captive portal URL " + url);
+        }
+        return null;
+    }
+
     private void testForCaptivePortal() {
         new Thread(new Runnable() {
             public void run() {
@@ -230,7 +262,7 @@
                 HttpURLConnection urlConnection = null;
                 int httpResponseCode = 500;
                 try {
-                    urlConnection = (HttpURLConnection) mURL.openConnection();
+                    urlConnection = (HttpURLConnection) mUrl.openConnection();
                     urlConnection.setInstanceFollowRedirects(false);
                     urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
                     urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
@@ -292,7 +324,7 @@
                 // settings.  Now prompt the WebView read the Network-specific proxy settings.
                 setWebViewProxy();
                 // Load the real page.
-                view.loadUrl(mURL.toString());
+                view.loadUrl(mUrl.toString());
                 return;
             } else if (mPagesLoaded == 2) {
                 // Prevent going back to empty first page.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 222cc5c..3e6c3f2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -119,7 +119,7 @@
             // and twice max user count for system and secure.
             final int size = 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
             try {
-                mBackingStore = new MemoryIntArray(size, false);
+                mBackingStore = new MemoryIntArray(size);
                 if (DEBUG) {
                     Slog.e(LOG_TAG, "Created backing store " + mBackingStore);
                 }
diff --git a/packages/SystemUI/res/color/qs_tile_text.xml b/packages/SystemUI/res/color/qs_tile_text.xml
deleted file mode 100644
index 90e0bce..0000000
--- a/packages/SystemUI/res/color/qs_tile_text.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ Copyright (C) 2015 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
-  -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false" android:color="@color/qs_tile_disabled_color" />
-    <item android:color="#B3FFFFFF" /> <!-- 70% white -->
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_no_sim.xml b/packages/SystemUI/res/drawable/ic_qs_no_sim.xml
index bd46012..2d831b2 100644
--- a/packages/SystemUI/res/drawable/ic_qs_no_sim.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_no_sim.xml
@@ -17,7 +17,8 @@
         android:width="32dp"
         android:height="32dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
 
     <path
         android:fillColor="#4DFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_0.xml b/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
index b78d3bf..0673848 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
@@ -18,7 +18,8 @@
         android:width="32.0dp"
         android:height="32.0dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
         android:fillAlpha="0.3"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_1.xml b/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
index e055de7..fbf9e71 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
@@ -18,7 +18,8 @@
         android:width="32.0dp"
         android:height="32.0dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:pathData="M10.0,14.6l-8.0,8.0l8.0,0.0l0,-8z"
         android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml b/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
index 71c40df..3a55623 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
@@ -17,7 +17,8 @@
         android:width="16.0dp"
         android:height="32dp"
         android:viewportWidth="12.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M3.500000,11.000000L1.800000,11.000000L1.800000,4.400000L0.200000,5.100000L0.200000,3.700000l3.100000,-1.300000l0.200000,0.000000L3.500000,11.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_2.xml b/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
index 8a48817..e9f5a0b 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
@@ -18,7 +18,8 @@
         android:width="32.0dp"
         android:height="32.0dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:pathData="M14.0,10.6l-12.0,12.0l12.0,0.0L14.0,10.6z"
         android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_3.xml b/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
index 39cc94c..769d648 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
@@ -18,7 +18,8 @@
         android:width="32.0dp"
         android:height="32.0dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
         android:fillAlpha="0.3"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
index e9a57ea..ddd8065 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
@@ -17,7 +17,8 @@
         android:width="17.333334dp"
         android:height="32dp"
         android:viewportWidth="13.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M2.000000,6.000000l0.800000,0.000000c0.300000,0.000000 0.500000,-0.100000 0.700000,-0.300000s0.200000,-0.500000 0.200000,-0.900000c0.000000,-0.300000 -0.100000,-0.600000 -0.200000,-0.800000S3.200000,3.700000 2.900000,3.700000C2.700000,3.700000 2.500000,3.800000 2.300000,4.000000S2.100000,4.400000 2.100000,4.700000L0.500000,4.700000C0.500000,4.000000 0.700000,3.400000 1.100000,3.000000s1.000000,-0.600000 1.700000,-0.600000c0.800000,0.000000 1.400000,0.200000 1.900000,0.600000s0.700000,1.000000 0.700000,1.800000c0.000000,0.400000 -0.100000,0.700000 -0.300000,1.100000S4.600000,6.500000 4.300000,6.600000C4.700000,6.800000 5.000000,7.100000 5.200000,7.400000s0.300000,0.700000 0.300000,1.200000c0.000000,0.800000 -0.200000,1.400000 -0.700000,1.800000s-1.100000,0.700000 -1.900000,0.700000c-0.700000,0.000000 -1.300000,-0.200000 -1.800000,-0.600000s-0.700000,-1.000000 -0.700000,-1.800000L2.000000,8.700000C2.000000,9.000000 2.100000,9.300000 2.300000,9.500000s0.400000,0.300000 0.600000,0.300000c0.300000,0.000000 0.500000,-0.100000 0.700000,-0.300000S3.900000,9.000000 3.900000,8.600000c0.000000,-0.500000 -0.100000,-0.800000 -0.300000,-1.000000S3.200000,7.300000 2.800000,7.300000L2.000000,7.300000L2.000000,6.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
index 012e95e..1bec1b8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
@@ -18,7 +18,8 @@
         android:width="32.0dp"
         android:height="32.0dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
         android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
index 42045d1..8e1f8eb 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
@@ -17,7 +17,8 @@
         android:width="16.0dp"
         android:height="32dp"
         android:viewportWidth="12.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M4.600000,7.800000l0.700000,0.000000l0.000000,1.300000L4.600000,9.100000L4.600000,11.000000L3.000000,11.000000L3.000000,9.200000L0.100000,9.200000L0.000000,8.100000L3.000000,2.500000l1.700000,0.000000L4.700000,7.800000zM1.600000,7.800000L3.000000,7.800000l0.000000,-3.000000L2.900000,5.000000L1.600000,7.800000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
index 4d7f325..e0c6b68 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
@@ -17,7 +17,8 @@
         android:width="24.0dp"
         android:height="24.0dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M4.6,7.8l0.7,0.0l0.0,1.3L4.6,9.1L4.6,11.0L3.0,11.0L3.0,9.2L0.1,9.2L0.0,8.2l3.0,-5.7l1.7,0.0L4.6,7.8L4.6,7.8zM1.7,7.8L3.0,7.8l0.0,-3.0L2.9,5.0L1.7,7.8z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
index 96e2fd4..1c068e5 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
@@ -17,7 +17,8 @@
         android:width="32dp"
         android:height="32dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:name="dot1"
         android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
index 4f253e3..0a85392 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
@@ -18,7 +18,8 @@
         android:width="32dp"
         android:height="32dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#4DFFFFFF"
         android:pathData="M21.799999,22.299999l-1.199999,-1.299999 0.000000,0.000000 -9.600000,-10.000000 0.000000,0.000000 -6.400000,-6.700000 -1.300000,1.300000 6.400000,6.700000 -8.700000,8.700000 16.900000,0.000000 2.600000,2.700001z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_e.xml b/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
index e49a409..4c90421 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
@@ -17,7 +17,8 @@
         android:width="6.6666665dp"
         android:height="32dp"
         android:viewportWidth="5.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M4.400000,7.300000L1.700000,7.300000l0.000000,2.400000l3.300000,0.000000L5.000000,11.000000L0.000000,11.000000L0.000000,2.500000l4.900000,0.000000l0.000000,1.300000L1.700000,3.800000l0.000000,2.100000l2.800000,0.000000L4.500000,7.300000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml
index 326373d..db4df76 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml
@@ -18,7 +18,8 @@
         android:width="32dp"
         android:height="32dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#4DFFFFFF"
         android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml
index 8baa4eb..6e5439d 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml
@@ -18,7 +18,8 @@
         android:width="32dp"
         android:height="32dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#4DFFFFFF"
         android:pathData="M2.0,22.0l20.0,0.0 0.0,-20.0z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml
index bf19a71..194edc3 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml
@@ -18,7 +18,8 @@
         android:width="32dp"
         android:height="32dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#4DFFFFFF"
         android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml
index 01839e85..b57af5c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml
@@ -18,7 +18,8 @@
         android:width="32dp"
         android:height="32dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#4DFFFFFF"
         android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml
index 48151ad..7d754a8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml
@@ -18,7 +18,8 @@
         android:width="32dp"
         android:height="32dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
index 9d42a44..64aadf9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
@@ -17,7 +17,8 @@
         android:width="9.333333dp"
         android:height="32dp"
         android:viewportWidth="7.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M6.500000,9.900000c-0.200000,0.400000 -0.600000,0.700000 -1.000000,0.900000s-1.000000,0.400000 -1.800000,0.400000c-0.900000,0.000000 -1.700000,-0.300000 -2.200000,-0.800000S0.700000,9.000000 0.700000,7.900000L0.700000,5.600000c0.000000,-1.100000 0.300000,-1.900000 0.800000,-2.400000s1.200000,-0.800000 2.100000,-0.800000c1.000000,0.000000 1.700000,0.200000 2.100000,0.700000s0.700000,1.200000 0.700000,2.100000L4.700000,5.200000c0.000000,-0.500000 -0.100000,-0.900000 -0.200000,-1.100000S4.000000,3.700000 3.600000,3.700000c-0.400000,0.000000 -0.700000,0.200000 -0.900000,0.500000S2.300000,5.000000 2.300000,5.600000l0.000000,2.300000c0.000000,0.700000 0.100000,1.100000 0.300000,1.400000s0.600000,0.500000 1.000000,0.500000c0.300000,0.000000 0.600000,0.000000 0.700000,-0.100000s0.300000,-0.200000 0.400000,-0.300000L4.700000,7.800000L3.500000,7.800000L3.500000,6.600000l2.900000,0.000000L6.400000,9.900000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_h.xml b/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
index f509d71..31918a9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
@@ -17,7 +17,8 @@
         android:width="8.0dp"
         android:height="32dp"
         android:viewportWidth="6.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M6.000000,11.000000L4.400000,11.000000L4.400000,7.500000L1.700000,7.500000L1.700000,11.000000L0.000000,11.000000L0.000000,2.500000l1.700000,0.000000l0.000000,3.700000l2.700000,0.000000L4.400000,2.500000L6.000000,2.500000L6.000000,11.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml b/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
index b7242e6..8766075 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
@@ -17,7 +17,8 @@
         android:width="17.333334dp"
         android:height="32dp"
         android:viewportWidth="13.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M2.000000,9.700000l2.000000,0.000000L4.000000,11.000000L0.300000,11.000000L0.300000,2.500000L2.000000,2.500000L2.000000,9.700000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml b/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
index 3af0629..5ff7d85 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
@@ -17,7 +17,8 @@
         android:width="24.0dp"
         android:height="24.0dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M2.0,9.7l2.0,0.0L4.0,11.0L0.4,11.0L0.4,2.5L2.0,2.5L2.0,9.7z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml b/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml
index f7fd97c..4e65004 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml
@@ -18,7 +18,8 @@
         android:width="32dp"
         android:height="32dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#4DFFFFFF"
         android:pathData="M2.000000,22.000000l20.000000,0.000000L22.000000,2.000000L2.000000,22.000000zM20.000000,20.000000L6.800000,20.000000L20.000000,6.800000L20.000000,20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_r.xml b/packages/SystemUI/res/drawable/ic_qs_signal_r.xml
index 66f64c9..2c10dc3f 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_r.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_r.xml
@@ -17,7 +17,8 @@
         android:width="8.0dp"
         android:height="32dp"
         android:viewportWidth="6.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
         android:pathData="M2.800000,7.900000l-1.000000,0.000000L1.800000,11.000000L0.200000,11.000000L0.200000,2.500000l2.700000,0.000000c0.900000,0.000000 1.500000,0.200000 2.000000,0.700000s0.700000,1.100000 0.700000,1.900000c0.000000,0.600000 -0.100000,1.100000 -0.300000,1.500000S4.800000,7.200000 4.400000,7.400000l1.500000,3.500000L5.900000,11.000000L4.100000,11.000000L2.800000,7.900000zM1.800000,6.500000l1.100000,0.000000c0.400000,0.000000 0.600000,-0.100000 0.800000,-0.400000S4.000000,5.600000 4.000000,5.200000c0.000000,-0.400000 -0.100000,-0.800000 -0.300000,-1.000000S3.300000,3.800000 2.900000,3.800000L1.800000,3.800000L1.800000,6.500000z"/>
diff --git a/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml b/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml
deleted file mode 100644
index 71400db..0000000
--- a/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2014 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-
-    <path
-        android:fillColor="@color/qs_tile_text"
-        android:pathData="M14.0,20.0l10.0,10.0 10.0,-10.0z"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index cce9c0f..5b0f0df 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -22,7 +22,7 @@
      <TextView android:id="@+id/tile_label"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textColor="@color/qs_tile_text"
+            android:textColor="?android:attr/textColorSecondary"
             android:gravity="center_horizontal"
             android:minLines="2"
             android:padding="0dp"
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index 73f26e2..e4ea08e 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -42,7 +42,7 @@
             android:singleLine="true"
             android:ellipsize="start"
             android:inputType="textShortMessage|textAutoCorrect|textCapSentences"
-            android:imeOptions="actionNone|flagNoExtractUi|flagNoFullscreen" />
+            android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
 
     <FrameLayout
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 29b5b62..a7d4aa0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -81,6 +81,9 @@
     <!-- Height of a heads up notification in the status bar -->
     <dimen name="notification_max_heads_up_height">148dp</dimen>
 
+    <!-- a threshold in dp per second that is considered fast scrolling -->
+    <dimen name="scroll_fast_threshold">1500dp</dimen>
+
     <!-- Height of a the shelf with the notification icons -->
     <dimen name="notification_shelf_height">32dp</dimen>
 
@@ -94,6 +97,9 @@
     <!-- The amount the content shifts upwards when transforming into the icon -->
     <dimen name="notification_icon_transform_content_shift">32dp</dimen>
 
+    <!-- The padding on the bottom of the notifications on the keyguard -->
+    <dimen name="keyguard_indication_bottom_padding">12sp</dimen>
+
     <!-- Minimum layouted height of a notification in the statusbar-->
     <dimen name="min_notification_layout_height">48dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index c6dde46..09671e7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -156,6 +156,10 @@
         @Override
         public void onListenerRegistered(IPinnedStackController controller) {
             mPinnedStackController = controller;
+
+            // Update the controller with the current tuner state
+            setMinimizedState(mIsMinimized);
+            setSnapToEdge(mEnableSnapToEdge);
         }
 
         @Override
@@ -353,10 +357,13 @@
      */
     private void setSnapToEdge(boolean snapToEdge) {
         mSnapAlgorithm.setSnapToEdge(snapToEdge);
-        try {
-            mPinnedStackController.setSnapToEdge(snapToEdge);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not set snap mode to edge", e);
+
+        if (mPinnedStackController != null) {
+            try {
+                mPinnedStackController.setSnapToEdge(snapToEdge);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not set snap mode to edge", e);
+            }
         }
     }
 
@@ -365,10 +372,13 @@
      */
     private void setMinimizedState(boolean isMinimized) {
         mIsMinimized = isMinimized;
-        try {
-            mPinnedStackController.setIsMinimized(isMinimized);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not set minimized state", e);
+
+        if (mPinnedStackController != null) {
+            try {
+                mPinnedStackController.setIsMinimized(isMinimized);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not set minimized state", e);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index d46d267..cf8d332 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -103,7 +103,7 @@
     private boolean mDimmed;
     private boolean mDark;
 
-    private int mBgTint = 0;
+    private int mBgTint = NO_COLOR;
     private float mBgAlpha = 1f;
 
     /**
@@ -507,8 +507,10 @@
      * Sets the tint color of the background
      */
     public void setTintColor(int color, boolean animated) {
-        mBgTint = color;
-        updateBackgroundTint(animated);
+        if (color != mBgTint) {
+            mBgTint = color;
+            updateBackgroundTint(animated);
+        }
     }
 
     /**
@@ -567,13 +569,15 @@
     }
 
     private void setBackgroundTintColor(int color) {
-        mCurrentBackgroundTint = color;
-        if (color == mNormalColor) {
-            // We don't need to tint a normal notification
-            color = 0;
+        if (color != mCurrentBackgroundTint) {
+            mCurrentBackgroundTint = color;
+            if (color == mNormalColor) {
+                // We don't need to tint a normal notification
+                color = 0;
+            }
+            mBackgroundDimmed.setTint(color);
+            mBackgroundNormal.setTint(color);
         }
-        mBackgroundDimmed.setTint(color);
-        mBackgroundNormal.setTint(color);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 661cc3c..e89fe55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -193,7 +193,7 @@
     private View mChildAfterViewWhenDismissed;
     private View mGroupParentWhenDismissed;
     private boolean mRefocusOnDismiss;
-    private float mIconTransformationAmount;
+    private float mContentTransformationAmount;
     private boolean mIconsVisible = true;
     private boolean mAboveShelf;
     private boolean mIsLastChild;
@@ -837,23 +837,29 @@
     /**
      * Set how much this notification is transformed into an icon.
      *
-     * @param iconTransformationAmount A value from 0 to 1 indicating how much we are transformed
-     *                                 to an icon
+     * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed
+     *                                 to the content away
      * @param isLastChild is this the last child in the list. If true, then the transformation is
      *                    different since it's content fades out.
      */
-    public void setIconTransformationAmount(float iconTransformationAmount, boolean isLastChild) {
+    public void setContentTransformationAmount(float contentTransformationAmount,
+            boolean isLastChild) {
         boolean changeTransformation = isLastChild != mIsLastChild;
-        changeTransformation |= mIconTransformationAmount != iconTransformationAmount;
+        changeTransformation |= mContentTransformationAmount != contentTransformationAmount;
         mIsLastChild = isLastChild;
-        mIconTransformationAmount = iconTransformationAmount;
+        mContentTransformationAmount = contentTransformationAmount;
         if (changeTransformation) {
             updateContentTransformation();
-            boolean iconsVisible = mIconTransformationAmount == 0.0f;
-            if (iconsVisible != mIconsVisible) {
-                mIconsVisible = iconsVisible;
-                updateIconVisibilities();
-            }
+        }
+    }
+
+    /**
+     * Set the icons to be visible of this notification.
+     */
+    public void setIconsVisible(boolean iconsVisible) {
+        if (iconsVisible != mIconsVisible) {
+            mIconsVisible = iconsVisible;
+            updateIconVisibilities();
         }
     }
 
@@ -864,9 +870,9 @@
 
     private void updateContentTransformation() {
         float contentAlpha;
-        float translationY = - mIconTransformationAmount * mIconTransformContentShift;
+        float translationY = -mContentTransformationAmount * mIconTransformContentShift;
         if (mIsLastChild) {
-            contentAlpha = 1.0f - mIconTransformationAmount;
+            contentAlpha = 1.0f - mContentTransformationAmount;
             contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f);
             contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha);
             translationY *= 0.4f;
@@ -885,7 +891,9 @@
     }
 
     private void updateIconVisibilities() {
-        boolean visible = isChildInGroup() || isBelowSpeedBump() || mIconsVisible;
+        boolean visible = isChildInGroup()
+                || (isBelowSpeedBump() && !NotificationShelf.SHOW_AMBIENT_ICONS)
+                || mIconsVisible;
         mPublicLayout.setIconsVisible(visible);
         mPrivateLayout.setIconsVisible(visible);
         if (mChildrenContainer != null) {
@@ -1679,13 +1687,17 @@
 
     @Override
     public void setClipBottomAmount(int clipBottomAmount) {
-        super.setClipBottomAmount(clipBottomAmount);
-        mPrivateLayout.setClipBottomAmount(clipBottomAmount);
-        mPublicLayout.setClipBottomAmount(clipBottomAmount);
-        if (mGuts != null) {
-            mGuts.setClipBottomAmount(clipBottomAmount);
+        if (clipBottomAmount != mClipBottomAmount) {
+            super.setClipBottomAmount(clipBottomAmount);
+            mPrivateLayout.setClipBottomAmount(clipBottomAmount);
+            mPublicLayout.setClipBottomAmount(clipBottomAmount);
+            if (mGuts != null) {
+                mGuts.setClipBottomAmount(clipBottomAmount);
+            }
         }
         if (mChildrenContainer != null) {
+            // We have to update this even if it hasn't changed, since the children locations can
+            // have changed
             mChildrenContainer.setClipBottomAmount(clipBottomAmount);
         }
     }
@@ -1871,8 +1883,8 @@
         }
 
         @Override
-        protected void onYTranslationAnimationFinished() {
-            super.onYTranslationAnimationFinished();
+        protected void onYTranslationAnimationFinished(View view) {
+            super.onYTranslationAnimationFinished(view);
             if (mHeadsupDisappearRunning) {
                 setHeadsUpAnimatingAway(false);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 3687f6d..32f8ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -208,6 +208,12 @@
                 expandedIcon = null;
                 throw new IconException("Couldn't create icon: " + ic);
             }
+            expandedIcon.setOnVisibilityChangedListener(
+                    newVisibility -> {
+                        if (row != null) {
+                            row.setIconsVisible(newVisibility != View.VISIBLE);
+                        }
+                    });
         }
 
         public void setIconTag(int key, Object tag) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 680562a..324eb2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.os.SystemProperties;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
@@ -34,19 +35,18 @@
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackScrollState;
 
-import java.util.ArrayList;
-import java.util.WeakHashMap;
-
 /**
  * A notification shelf view that is placed inside the notification scroller. It manages the
  * overflow icons that don't fit into the regular list anymore.
  */
 public class NotificationShelf extends ActivatableNotificationView {
 
+    public static final boolean SHOW_AMBIENT_ICONS = true;
+    private static final boolean USE_ANIMATIONS_WHEN_OPENING =
+            SystemProperties.getBoolean("debug.icon_opening_animations", true);
     private ViewInvertHelper mViewInvertHelper;
     private boolean mDark;
     private NotificationIconContainer mShelfIcons;
-    private ArrayList<StatusBarIconView> mIcons = new ArrayList<>();
     private ShelfState mShelfState;
     private int[] mTmp = new int[2];
     private boolean mHideBackground;
@@ -60,6 +60,7 @@
     private int mNotGoneIndex;
     private boolean mHasItemsInStableShelf;
     private NotificationIconContainer mCollapsedIcons;
+    private int mScrollFastThreshold;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -101,6 +102,8 @@
         setLayoutParams(layoutParams);
         int padding = getResources().getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
         mShelfIcons.setPadding(padding, 0, padding, 0);
+        mScrollFastThreshold = getResources().getDimensionPixelOffset(
+                R.dimen.scroll_fast_threshold);
     }
 
     @Override
@@ -162,6 +165,7 @@
                 mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex);
             }
             mShelfState.hasItemsInStableShelf = lastViewState.inShelf;
+            mShelfState.hidden = !mAmbientState.isShadeExpanded();
         } else {
             mShelfState.hidden = true;
             mShelfState.location = ExpandableViewState.LOCATION_GONE;
@@ -174,15 +178,15 @@
      * the icons from the notification area into the shelf.
      */
     public void updateAppearance() {
-        WeakHashMap<View, NotificationIconContainer.IconState> iconStates =
-                mShelfIcons.resetViewStates();
+        mShelfIcons.resetViewStates();
+        float shelfStart = getTranslationY();
         float numViewsInShelf = 0.0f;
         View lastChild = mAmbientState.getLastVisibleBackgroundChild();
         mNotGoneIndex = -1;
         float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
         float expandAmount = 0.0f;
-        if (getTranslationY() >= interpolationStart) {
-            expandAmount = (getTranslationY() - interpolationStart) / getIntrinsicHeight();
+        if (shelfStart >= interpolationStart) {
+            expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight();
             expandAmount = Math.min(1.0f, expandAmount);
         }
         //  find the first view that doesn't overlap with the shelf
@@ -196,6 +200,8 @@
         int colorTwoBefore = NO_COLOR;
         int previousColor = NO_COLOR;
         float transitionAmount = 0.0f;
+        boolean scrollingFast = mAmbientState.getCurrentScrollVelocity() > mScrollFastThreshold;
+        int baseZHeight = mAmbientState.getBaseZHeight();
         while (notificationIndex < mHostLayout.getChildCount()) {
             ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
             notificationIndex++;
@@ -204,30 +210,28 @@
                 continue;
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            StatusBarIconView icon = row.getEntry().expandedIcon;
-            NotificationIconContainer.IconState iconState = iconStates.get(icon);
             float notificationClipEnd;
-            float shelfStart = getTranslationY();
-            boolean aboveShelf = row.getTranslationZ() > mAmbientState.getBaseZHeight();
+            boolean aboveShelf = row.getTranslationZ() > baseZHeight;
             boolean isLastChild = child == lastChild;
+            float rowTranslationY = row.getTranslationY();
             if (isLastChild || aboveShelf || backgroundForceHidden) {
                 notificationClipEnd = shelfStart + getIntrinsicHeight();
             } else {
                 notificationClipEnd = shelfStart - mPaddingBetweenElements;
-                float height = notificationClipEnd - row.getTranslationY();
+                float height = notificationClipEnd - rowTranslationY;
                 if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) {
                     // We want the gap to close when we reached the minimum size and only shrink
                     // before
                     notificationClipEnd = Math.min(shelfStart,
-                            row.getTranslationY() + getNotificationMergeSize());
+                            rowTranslationY + getNotificationMergeSize());
                 }
             }
             updateNotificationClipHeight(row, notificationClipEnd);
-            float inShelfAmount = updateIconAppearance(row, iconState, icon, expandAmount,
+            float inShelfAmount = updateIconAppearance(row, expandAmount, scrollingFast,
                     isLastChild);
             numViewsInShelf += inShelfAmount;
             int ownColorUntinted = row.getBackgroundColorWithoutTint();
-            if (row.getTranslationY() >= getTranslationY() && mNotGoneIndex == -1) {
+            if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) {
                 mNotGoneIndex = notGoneIndex;
                 setTintColor(previousColor);
                 setOverrideTintColor(colorTwoBefore, transitionAmount);
@@ -248,11 +252,9 @@
             notGoneIndex++;
             previousColor = ownColorUntinted;
         }
+        mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
         mShelfIcons.calculateIconTranslations();
         mShelfIcons.applyIconStates();
-        setVisibility(numViewsInShelf != 0.0f && mAmbientState.isShadeExpanded()
-                ? VISIBLE
-                : INVISIBLE);
         boolean hideBackground = numViewsInShelf < 1.0f;
         setHideBackground(hideBackground || backgroundForceHidden);
         if (mNotGoneIndex == -1) {
@@ -275,41 +277,109 @@
     /**
      * @return the icon amount how much this notification is in the shelf;
      */
-    private float updateIconAppearance(ExpandableNotificationRow row,
-            NotificationIconContainer.IconState iconState, StatusBarIconView icon,
-            float expandAmount, boolean isLastChild) {
+    private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount,
+            boolean scrollingFast, boolean isLastChild) {
         // Let calculate how much the view is in the shelf
         float viewStart = row.getTranslationY();
-        int transformHeight = row.getActualHeight() + mPaddingBetweenElements;
+        int fullHeight = row.getActualHeight() + mPaddingBetweenElements;
+        float iconTransformDistance = getIntrinsicHeight() * 1.5f;
         if (isLastChild) {
-            transformHeight =
-                    Math.min(transformHeight, row.getMinHeight() - getIntrinsicHeight());
+            fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight());
+            iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight()
+                    - getIntrinsicHeight());
         }
-        float viewEnd = viewStart + transformHeight;
-        float iconAppearAmount;
-        float yTranslation;
-        float alpha = 1.0f;
-        if (viewEnd >= getTranslationY() && (mAmbientState.isShadeExpanded()
+        float viewEnd = viewStart + fullHeight;
+        float fullTransitionAmount;
+        float iconTransitionAmount;
+        float shelfStart = getTranslationY();
+        if (viewEnd >= shelfStart && (mAmbientState.isShadeExpanded()
                 || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
-            if (viewStart < getTranslationY()) {
-                float linearAmount = (getTranslationY() - viewStart) / transformHeight;
+            if (viewStart < shelfStart) {
+
+                float fullAmount = (shelfStart - viewStart) / fullHeight;
                 float interpolatedAmount =  Interpolators.ACCELERATE_DECELERATE.getInterpolation(
-                        linearAmount);
+                        fullAmount);
                 interpolatedAmount = NotificationUtils.interpolate(
-                        interpolatedAmount, linearAmount, expandAmount);
-                iconAppearAmount = 1.0f - interpolatedAmount;
+                        interpolatedAmount, fullAmount, expandAmount);
+                fullTransitionAmount = 1.0f - interpolatedAmount;
+
+                iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance;
+                iconTransitionAmount = Math.min(1.0f, iconTransitionAmount);
+                iconTransitionAmount = 1.0f - iconTransitionAmount;
+
             } else {
-                iconAppearAmount = 1.0f;
+                fullTransitionAmount = 1.0f;
+                iconTransitionAmount = 1.0f;
             }
         } else {
-            iconAppearAmount = 0.0f;
+            fullTransitionAmount = 0.0f;
+            iconTransitionAmount = 0.0f;
         }
+        updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount, scrollingFast,
+                isLastChild);
+        return fullTransitionAmount;
+    }
 
-        // Lets now calculate how much of the transformation has already happened. This is different
-        // from the above, since we only start transforming when the view is already quite a bit
-        // pushed in.
+    private void updateIconPositioning(ExpandableNotificationRow row, float iconTransitionAmount,
+            float fullTransitionAmount, boolean scrollingFast, boolean isLastChild) {
+        StatusBarIconView icon = row.getEntry().expandedIcon;
+        NotificationIconContainer.IconState iconState = getIconState(icon);
+        if (iconState == null) {
+            return;
+        }
+        float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
+        if (clampedAmount == iconTransitionAmount) {
+            iconState.keepClampedPosition = false;
+        }
+        if (clampedAmount == fullTransitionAmount) {
+            iconState.useFullTransitionAmount = fullTransitionAmount == 0.0f || scrollingFast;
+            iconState.translateContent = mMaxLayoutHeight - getTranslationY()
+                    - getIntrinsicHeight() > 0;
+        }
+        float transitionAmount;
+        boolean needCannedAnimation = iconState.clampedAppearAmount == 1.0f
+                && clampedAmount == 0.0f;
+        if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount) {
+            transitionAmount = iconTransitionAmount;
+        } else if (iconState.keepClampedPosition
+                && iconState.clampedAppearAmount != clampedAmount) {
+            // We animated to the clamped amount but then decided to go the other way. Let's
+            // animate it to the new position
+            transitionAmount = iconTransitionAmount;
+            iconState.needsCannedAnimation = true;
+            iconState.keepClampedPosition = false;
+        } else if (needCannedAnimation || iconState.keepClampedPosition
+                || iconState.iconAppearAmount == 1.0f) {
+            // We need to perform a canned animation since we crossed the treshhold
+            transitionAmount = clampedAmount;
+            iconState.keepClampedPosition = iconState.keepClampedPosition || needCannedAnimation;
+            iconState.needsCannedAnimation = needCannedAnimation;
+        } else {
+            transitionAmount = iconTransitionAmount;
+        }
+        iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING
+                    || iconState.useFullTransitionAmount
+                ? fullTransitionAmount
+                : transitionAmount;
+        iconState.clampedAppearAmount = clampedAmount;
+        setIconTransformationAmount(row, transitionAmount);
+        float contentTransformationAmount = isLastChild || iconState.translateContent
+                ? iconTransitionAmount
+                : 0.0f;
+        row.setContentTransformationAmount(contentTransformationAmount, isLastChild);
+    }
+
+    private boolean isLastChild(ExpandableNotificationRow row) {
+        return row == mAmbientState.getLastVisibleBackgroundChild();
+    }
+
+    private void setIconTransformationAmount(ExpandableNotificationRow row,
+            float transitionAmount) {
+        StatusBarIconView icon = row.getEntry().expandedIcon;
+        NotificationIconContainer.IconState iconState = getIconState(icon);
+
         View rowIcon = row.getNotificationIcon();
-        float notificationIconPosition = viewStart;
+        float notificationIconPosition = row.getTranslationY();
         float notificationIconSize = 0.0f;
         int iconTopPadding;
         if (rowIcon != null) {
@@ -322,28 +392,18 @@
         float shelfIconPosition = getTranslationY() + icon.getTop();
         shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f;
         float transitionDistance = getIntrinsicHeight() * 1.5f;
-        if (isLastChild) {
+        if (row == mAmbientState.getLastVisibleBackgroundChild()) {
             transitionDistance = Math.min(transitionDistance, row.getMinHeight()
                     - getIntrinsicHeight());
         }
         float transformationStartPosition = getTranslationY() - transitionDistance;
-        float transitionAmount = 0.0f;
-        if (viewStart < transformationStartPosition
-                || (!mAmbientState.isShadeExpanded()
-                        && (row.isPinned() || row.isHeadsUpAnimatingAway()))) {
-            // We simply place it on the icon of the notification
-            yTranslation = notificationIconPosition - shelfIconPosition;
-        } else {
-            transitionAmount = (viewStart - transformationStartPosition)
-                    / transitionDistance;
-            float startPosition = transformationStartPosition + iconTopPadding;
-            yTranslation = NotificationUtils.interpolate(
-                    startPosition - shelfIconPosition, 0, transitionAmount);
-            // If we are merging into the shelf, lets make sure the shelf is at least on our height,
-            // otherwise the icons won't be visible.
-            setTranslationZ(Math.max(getTranslationZ(), row.getTranslationZ()));
-        }
+        float iconYTranslation = NotificationUtils.interpolate(
+                Math.min(notificationIconPosition, transformationStartPosition + iconTopPadding)
+                        - shelfIconPosition,
+                0,
+                transitionAmount);
         float shelfIconSize = icon.getHeight() * icon.getIconScale();
+        float alpha = 1.0f;
         if (!row.isShowingIcon()) {
             // The view currently doesn't have an icon, lets transform it in!
             alpha = transitionAmount;
@@ -352,15 +412,12 @@
         // The notification size is different from the size in the shelf / statusbar
         float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
                 transitionAmount);
-        row.setIconTransformationAmount(transitionAmount, isLastChild);
         if (iconState != null) {
             iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale();
             iconState.scaleY = iconState.scaleX;
             iconState.hidden = transitionAmount == 0.0f;
-            iconState.iconAppearAmount = iconAppearAmount;
             iconState.alpha = alpha;
-            iconState.yTranslation = yTranslation;
-            icon.setVisibility(transitionAmount == 0.0f ? INVISIBLE : VISIBLE);
+            iconState.yTranslation = iconYTranslation;
             if (row.isInShelf() && !row.isTransformingIntoShelf()) {
                 iconState.iconAppearAmount = 1.0f;
                 iconState.alpha = 1.0f;
@@ -368,8 +425,14 @@
                 iconState.scaleY = 1.0f;
                 iconState.hidden = false;
             }
+            if (row.isAboveShelf()) {
+                iconState.hidden = true;
+            }
         }
-        return iconAppearAmount;
+    }
+
+    private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) {
+        return mShelfIcons.getIconState(icon);
     }
 
     private float getFullyClosedTranslation() {
@@ -386,9 +449,11 @@
     }
 
     private void setHideBackground(boolean hideBackground) {
-        mHideBackground = hideBackground;
-        updateBackground();
-        updateOutline();
+        if (mHideBackground != hideBackground) {
+            mHideBackground = hideBackground;
+            updateBackground();
+            updateOutline();
+        }
     }
 
     public boolean hidesBackground() {
@@ -415,13 +480,23 @@
                 mShelfIcons.getWidth(),
                 openedAmount);
         mShelfIcons.setActualLayoutWidth(width);
-        float padding = NotificationUtils.interpolate(mCollapsedIcons.getPaddingEnd(),
+        boolean hasOverflow = mCollapsedIcons.hasOverflow();
+        int collapsedPadding = mCollapsedIcons.getPaddingEnd();
+        if (!hasOverflow) {
+            // we have to ensure that adding the low priority notification won't lead to an
+            // overflow
+            collapsedPadding -= (1.0f + NotificationIconContainer.OVERFLOW_EARLY_AMOUNT)
+                    * mCollapsedIcons.getIconSize();
+        }
+        float padding = NotificationUtils.interpolate(collapsedPadding,
                 mShelfIcons.getPaddingEnd(),
                 openedAmount);
         mShelfIcons.setActualPaddingEnd(padding);
         float paddingStart = NotificationUtils.interpolate(start,
                 mShelfIcons.getPaddingStart(), openedAmount);
         mShelfIcons.setActualPaddingStart(paddingStart);
+        mShelfIcons.setOpenedAmount(openedAmount);
+        mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption());
     }
 
     public void setMaxLayoutHeight(int maxLayoutHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index bfa43fd..dba7130 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -42,7 +42,6 @@
     private float mViewAlpha = 1.0f;
     private ValueAnimator mAlphaAnimator;
     private Rect mExcludedRect = new Rect();
-    private int mLeftInset = 0;
     private boolean mHasExcludedArea;
     private ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener
             = new ValueAnimator.AnimatorUpdateListener() {
@@ -88,12 +87,12 @@
                 if (mExcludedRect.top > 0) {
                     canvas.drawRect(0, 0, getWidth(), mExcludedRect.top, mPaint);
                 }
-                if (mExcludedRect.left + mLeftInset > 0) {
-                    canvas.drawRect(0,  mExcludedRect.top, mExcludedRect.left + mLeftInset,
-                            mExcludedRect.bottom, mPaint);
+                if (mExcludedRect.left > 0) {
+                    canvas.drawRect(0,  mExcludedRect.top, mExcludedRect.left, mExcludedRect.bottom,
+                            mPaint);
                 }
-                if (mExcludedRect.right + mLeftInset < getWidth()) {
-                    canvas.drawRect(mExcludedRect.right + mLeftInset,
+                if (mExcludedRect.right < getWidth()) {
+                    canvas.drawRect(mExcludedRect.right,
                             mExcludedRect.top,
                             getWidth(),
                             mExcludedRect.bottom,
@@ -184,14 +183,4 @@
     public void setChangeRunnable(Runnable changeRunnable) {
         mChangeRunnable = changeRunnable;
     }
-
-    public void setLeftInset(int leftInset) {
-        if (mLeftInset != leftInset) {
-            mLeftInset = leftInset;
-
-            if (mHasExcludedArea) {
-                invalidate();
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index d635bb0..a2c2fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -37,6 +37,7 @@
 import android.util.Log;
 import android.util.Property;
 import android.util.TypedValue;
+import android.view.View;
 import android.view.ViewDebug;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Interpolator;
@@ -102,6 +103,7 @@
     private ObjectAnimator mIconAppearAnimator;
     private ObjectAnimator mDotAnimator;
     private float mDotAppearAmount;
+    private OnVisibilityChangedListener mOnVisibilityChangedListener;
 
     public StatusBarIconView(Context context, String slot, Notification notification) {
         this(context, slot, notification, false);
@@ -453,6 +455,7 @@
     }
 
     public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) {
+        boolean runnableAdded = false;
         if (visibleState != mVisibleState) {
             mVisibleState = visibleState;
             if (animate) {
@@ -465,20 +468,22 @@
                     targetAmount = 1.0f;
                     interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
                 }
-                mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
-                        targetAmount);
-                mIconAppearAnimator.setInterpolator(interpolator);
-                mIconAppearAnimator.setDuration(100);
-                mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mIconAppearAnimator = null;
-                        if (endRunnable != null) {
-                            endRunnable.run();
+                float currentAmount = getIconAppearAmount();
+                if (targetAmount != currentAmount) {
+                    mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
+                            currentAmount, targetAmount);
+                    mIconAppearAnimator.setInterpolator(interpolator);
+                    mIconAppearAnimator.setDuration(100);
+                    mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mIconAppearAnimator = null;
+                            runRunnable(endRunnable);
                         }
-                    }
-                });
-                mIconAppearAnimator.start();
+                    });
+                    mIconAppearAnimator.start();
+                    runnableAdded = true;
+                }
 
                 if (mDotAnimator != null) {
                     mDotAnimator.cancel();
@@ -489,22 +494,41 @@
                     targetAmount = 1.0f;
                     interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
                 }
-                mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
-                        targetAmount);
-                mDotAnimator.setInterpolator(interpolator);
-                mDotAnimator.setDuration(100);
-                mDotAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mDotAnimator = null;
-                    }
-                });
-                mDotAnimator.start();
+                currentAmount = getDotAppearAmount();
+                if (targetAmount != currentAmount) {
+                    mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
+                            currentAmount, targetAmount);
+                    mDotAnimator.setInterpolator(interpolator);
+                    mDotAnimator.setDuration(100);
+                    final boolean runRunnable = !runnableAdded;
+                    mDotAnimator.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mDotAnimator = null;
+                            if (runRunnable) {
+                                runRunnable(endRunnable);
+                            }
+                        }
+                    });
+                    mDotAnimator.start();
+                    runnableAdded = true;
+                }
             } else {
                 setIconAppearAmount(visibleState == STATE_ICON ? 1.0f : 0.0f);
-                setDotAppearAmount(visibleState == STATE_DOT ? 1.0f : 0.0f);
+                setDotAppearAmount(visibleState == STATE_DOT ? 1.0f
+                        : visibleState == STATE_ICON ? 2.0f
+                        : 0.0f);
             }
         }
+        if (!runnableAdded) {
+            runRunnable(endRunnable);
+        }
+    }
+
+    private void runRunnable(Runnable runnable) {
+        if (runnable != null) {
+            runnable.run();
+        }
     }
 
     public void setIconAppearAmount(float iconAppearAmount) {
@@ -525,7 +549,23 @@
         invalidate();
     }
 
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        if (mOnVisibilityChangedListener != null) {
+            mOnVisibilityChangedListener.onVisibilityChanged(visibility);
+        }
+    }
+
     public float getDotAppearAmount() {
         return mDotAppearAmount;
     }
+
+    public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) {
+        mOnVisibilityChangedListener = listener;
+    }
+
+    public interface OnVisibilityChangedListener {
+        void onVisibilityChanged(int newVisibility);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
index 1a46815..4969a1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
@@ -14,134 +14,16 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.app.ActivityManager;
-import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.os.UserHandle;
-import android.os.UserManager;
-
 import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
 import com.android.systemui.statusbar.policy.CallbackController;
 
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
+public interface ManagedProfileController extends CallbackController<Callback> {
 
-public class ManagedProfileController implements CallbackController<Callback> {
+    void setWorkModeEnabled(boolean enabled);
 
-    private final List<Callback> mCallbacks = new ArrayList<>();
+    boolean hasActiveProfile();
 
-    private final Context mContext;
-    private final UserManager mUserManager;
-    private final LinkedList<UserInfo> mProfiles;
-    private boolean mListening;
-    private int mCurrentUser;
-
-    public ManagedProfileController(QSTileHost host) {
-        mContext = host.getContext();
-        mUserManager = UserManager.get(mContext);
-        mProfiles = new LinkedList<UserInfo>();
-    }
-
-    public void addCallback(Callback callback) {
-        mCallbacks.add(callback);
-        if (mCallbacks.size() == 1) {
-            setListening(true);
-        }
-        callback.onManagedProfileChanged();
-    }
-
-    public void removeCallback(Callback callback) {
-        if (mCallbacks.remove(callback) && mCallbacks.size() == 0) {
-            setListening(false);
-        }
-    }
-
-    public void setWorkModeEnabled(boolean enableWorkMode) {
-        synchronized (mProfiles) {
-            for (UserInfo ui : mProfiles) {
-                if (enableWorkMode) {
-                    if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
-                        StatusBarManager statusBarManager = (StatusBarManager) mContext
-                                .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
-                        statusBarManager.collapsePanels();
-                    }
-                } else {
-                    mUserManager.setQuietModeEnabled(ui.id, true);
-                }
-            }
-        }
-    }
-
-    private void reloadManagedProfiles() {
-        synchronized (mProfiles) {
-            boolean hadProfile = mProfiles.size() > 0;
-            int user = ActivityManager.getCurrentUser();
-            mProfiles.clear();
-
-            for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
-                if (ui.isManagedProfile()) {
-                    mProfiles.add(ui);
-                }
-            }
-            if (mProfiles.size() == 0 && hadProfile && (user == mCurrentUser)) {
-                for (Callback callback : mCallbacks) {
-                    callback.onManagedProfileRemoved();
-                }
-            }
-            mCurrentUser = user;
-        }
-    }
-
-    public boolean hasActiveProfile() {
-        if (!mListening) reloadManagedProfiles();
-        synchronized (mProfiles) {
-            return mProfiles.size() > 0;
-        }
-    }
-
-    public boolean isWorkModeEnabled() {
-        if (!mListening) reloadManagedProfiles();
-        synchronized (mProfiles) {
-            for (UserInfo ui : mProfiles) {
-                if (ui.isQuietModeEnabled()) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    private void setListening(boolean listening) {
-        mListening = listening;
-        if (listening) {
-            reloadManagedProfiles();
-
-            final IntentFilter filter = new IntentFilter();
-            filter.addAction(Intent.ACTION_USER_SWITCHED);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
-            filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
-            mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null);
-        } else {
-            mContext.unregisterReceiver(mReceiver);
-        }
-    }
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            reloadManagedProfiles();
-            for (Callback callback : mCallbacks) {
-                callback.onManagedProfileChanged();
-            }
-        }
-    };
+    boolean isWorkModeEnabled();
 
     public interface Callback {
         void onManagedProfileChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
new file mode 100644
index 0000000..fc33ace
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 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.systemui.statusbar.phone;
+
+import android.app.ActivityManager;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ManagedProfileControllerImpl implements ManagedProfileController {
+
+    private final List<Callback> mCallbacks = new ArrayList<>();
+
+    private final Context mContext;
+    private final UserManager mUserManager;
+    private final LinkedList<UserInfo> mProfiles;
+    private boolean mListening;
+    private int mCurrentUser;
+
+    public ManagedProfileControllerImpl(QSTileHost host) {
+        mContext = host.getContext();
+        mUserManager = UserManager.get(mContext);
+        mProfiles = new LinkedList<UserInfo>();
+    }
+
+    public void addCallback(Callback callback) {
+        mCallbacks.add(callback);
+        if (mCallbacks.size() == 1) {
+            setListening(true);
+        }
+        callback.onManagedProfileChanged();
+    }
+
+    public void removeCallback(Callback callback) {
+        if (mCallbacks.remove(callback) && mCallbacks.size() == 0) {
+            setListening(false);
+        }
+    }
+
+    public void setWorkModeEnabled(boolean enableWorkMode) {
+        synchronized (mProfiles) {
+            for (UserInfo ui : mProfiles) {
+                if (enableWorkMode) {
+                    if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
+                        StatusBarManager statusBarManager = (StatusBarManager) mContext
+                                .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
+                        statusBarManager.collapsePanels();
+                    }
+                } else {
+                    mUserManager.setQuietModeEnabled(ui.id, true);
+                }
+            }
+        }
+    }
+
+    private void reloadManagedProfiles() {
+        synchronized (mProfiles) {
+            boolean hadProfile = mProfiles.size() > 0;
+            int user = ActivityManager.getCurrentUser();
+            mProfiles.clear();
+
+            for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
+                if (ui.isManagedProfile()) {
+                    mProfiles.add(ui);
+                }
+            }
+            if (mProfiles.size() == 0 && hadProfile && (user == mCurrentUser)) {
+                for (Callback callback : mCallbacks) {
+                    callback.onManagedProfileRemoved();
+                }
+            }
+            mCurrentUser = user;
+        }
+    }
+
+    public boolean hasActiveProfile() {
+        if (!mListening) reloadManagedProfiles();
+        synchronized (mProfiles) {
+            return mProfiles.size() > 0;
+        }
+    }
+
+    public boolean isWorkModeEnabled() {
+        if (!mListening) reloadManagedProfiles();
+        synchronized (mProfiles) {
+            for (UserInfo ui : mProfiles) {
+                if (ui.isQuietModeEnabled()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    private void setListening(boolean listening) {
+        mListening = listening;
+        if (listening) {
+            reloadManagedProfiles();
+
+            final IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_USER_SWITCHED);
+            filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+            filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+            filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+            filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+            mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null);
+        } else {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            reloadManagedProfiles();
+            for (Callback callback : mCallbacks) {
+                callback.onManagedProfileChanged();
+            }
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index d543f49..345dcbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -127,9 +127,9 @@
         return mPhoneStatusBar.getStatusBarHeight();
     }
 
-    protected boolean shouldShowNotification(NotificationData.Entry entry,
-            NotificationData notificationData) {
-        if (notificationData.isAmbient(entry.key)
+    protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
+            NotificationData notificationData, boolean showAmbient) {
+        if (notificationData.isAmbient(entry.key) && !showAmbient
                 && !NotificationData.showNotificationEvenIfUnprovisioned(entry.notification)) {
             return false;
         }
@@ -148,8 +148,10 @@
      */
     public void updateNotificationIcons(NotificationData notificationData) {
 
-        updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons);
-        updateIconsForLayout(notificationData, entry -> entry.expandedIcon, mShelfIcons);
+        updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons,
+                false /* showAmbient */);
+        updateIconsForLayout(notificationData, entry -> entry.expandedIcon, mShelfIcons,
+                NotificationShelf.SHOW_AMBIENT_ICONS);
 
         applyNotificationIconsTint();
         ArrayList<NotificationData.Entry> activeNotifications
@@ -173,10 +175,11 @@
      * @param notificationData the notification data to look up which notifications are relevant
      * @param function A function to look up an icon view based on an entry
      * @param hostLayout which layout should be updated
+     * @param showAmbient should ambient notification icons be shown
      */
     private void updateIconsForLayout(NotificationData notificationData,
             Function<NotificationData.Entry, StatusBarIconView> function,
-            NotificationIconContainer hostLayout) {
+            NotificationIconContainer hostLayout, boolean showAmbient) {
         ArrayList<StatusBarIconView> toShow = new ArrayList<>(
                 mNotificationScrollLayout.getChildCount());
 
@@ -185,7 +188,7 @@
             View view = mNotificationScrollLayout.getChildAt(i);
             if (view instanceof ExpandableNotificationRow) {
                 NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
-                if (shouldShowNotification(ent, notificationData)) {
+                if (shouldShowNotificationIcon(ent, notificationData, showAmbient)) {
                     toShow.add(function.apply(ent));
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 03697b8..d323e4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -31,15 +31,23 @@
 import com.android.systemui.statusbar.stack.AnimationProperties;
 import com.android.systemui.statusbar.stack.ViewState;
 
-import java.util.WeakHashMap;
+import java.util.HashMap;
 
 /**
  * A container for notification icons. It handles overflowing icons properly and positions them
  * correctly on the screen.
  */
 public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
+    /**
+     * A float value indicating how much before the overflow start the icons should transform into
+     * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
+     * 1 icon width early.
+     */
+    public static final float OVERFLOW_EARLY_AMOUNT = 0.2f;
+    private static final int NO_VALUE = Integer.MIN_VALUE;
     private static final String TAG = "NotificationIconContainer";
     private static final boolean DEBUG = false;
+    private static final int CANNED_ANIMATION_DURATION = 100;
     private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
         private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
 
@@ -49,6 +57,26 @@
         }
     }.setDuration(200);
 
+    private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() {
+        private AnimationFilter mAnimationFilter = new AnimationFilter().animateY().animateAlpha();
+        // TODO: add scale
+
+        @Override
+        public AnimationFilter getAnimationFilter() {
+            return mAnimationFilter;
+        }
+    }.setDuration(CANNED_ANIMATION_DURATION);
+
+    private static final AnimationProperties mTempProperties = new AnimationProperties() {
+        private AnimationFilter mAnimationFilter = new AnimationFilter();
+        // TODO: add scale
+
+        @Override
+        public AnimationFilter getAnimationFilter() {
+            return mAnimationFilter;
+        }
+    }.setDuration(CANNED_ANIMATION_DURATION);
+
     private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
         private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
 
@@ -59,14 +87,19 @@
     }.setDuration(200).setDelay(50);
 
     private boolean mShowAllIcons = true;
-    private WeakHashMap<View, IconState> mIconStates = new WeakHashMap<>();
+    private final HashMap<View, IconState> mIconStates = new HashMap<>();
     private int mDotPadding;
     private int mStaticDotRadius;
-    private int mActualLayoutWidth = -1;
-    private float mActualPaddingEnd = -1;
-    private float mActualPaddingStart = -1;
+    private int mActualLayoutWidth = NO_VALUE;
+    private float mActualPaddingEnd = NO_VALUE;
+    private float mActualPaddingStart = NO_VALUE;
     private boolean mChangingViewPositions;
-    private int mAnimationStartIndex = -1;
+    private int mAddAnimationStartIndex = -1;
+    private int mCannedAnimationStartIndex = -1;
+    private int mSpeedBumpIndex = -1;
+    private int mIconSize;
+    private float mOpenedAmount = 0.0f;
+    private float mVisualOverflowAdaption;
 
     public NotificationIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -97,6 +130,7 @@
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         float centerY = getHeight() / 2.0f;
         // we layout all our children on the left at the top
+        mIconSize = 0;
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
             // We need to layout all children even the GONE ones, such that the heights are
@@ -105,6 +139,9 @@
             int height = child.getMeasuredHeight();
             int top = (int) (centerY - height / 2.0f);
             child.layout(0, top, width, top + height);
+            if (i == 0) {
+                mIconSize = child.getWidth();
+            }
         }
         if (mShowAllIcons) {
             resetViewStates();
@@ -121,7 +158,8 @@
                 childState.applyToView(child);
             }
         }
-        mAnimationStartIndex = -1;
+        mAddAnimationStartIndex = -1;
+        mCannedAnimationStartIndex = -1;
     }
 
     @Override
@@ -133,10 +171,10 @@
         int childIndex = indexOfChild(child);
         if (childIndex < getChildCount() - 1
             && mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) {
-            if (mAnimationStartIndex < 0) {
-                mAnimationStartIndex = childIndex;
+            if (mAddAnimationStartIndex < 0) {
+                mAddAnimationStartIndex = childIndex;
             } else {
-                mAnimationStartIndex = Math.min(mAnimationStartIndex, childIndex);
+                mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
             }
         }
     }
@@ -149,10 +187,10 @@
             if (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
                     && child.getVisibility() == VISIBLE) {
                 int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX());
-                if (mAnimationStartIndex < 0) {
-                    mAnimationStartIndex = animationStartIndex;
+                if (mAddAnimationStartIndex < 0) {
+                    mAddAnimationStartIndex = animationStartIndex;
                 } else {
-                    mAnimationStartIndex = Math.min(mAnimationStartIndex, animationStartIndex);
+                    mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, animationStartIndex);
                 }
             }
             if (!mChangingViewPositions) {
@@ -177,14 +215,13 @@
         return getChildCount();
     }
 
-    public WeakHashMap<View, IconState> resetViewStates() {
+    public void resetViewStates() {
         for (int i = 0; i < getChildCount(); i++) {
             View view = getChildAt(i);
             ViewState iconState = mIconStates.get(view);
             iconState.initFrom(view);
             iconState.alpha = 1.0f;
         }
-        return mIconStates;
     }
 
     /**
@@ -194,42 +231,74 @@
      */
     public void calculateIconTranslations() {
         float translationX = getActualPaddingStart();
-        int overflowingIconIndex = -1;
-        int lastTwoIconWidth = 0;
+        int firstOverflowIndex = -1;
         int childCount = getChildCount();
+        float layoutEnd = getLayoutEnd();
+        float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
+        boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
+        float visualOverflowStart = 0;
         for (int i = 0; i < childCount; i++) {
             View view = getChildAt(i);
             IconState iconState = mIconStates.get(view);
             iconState.xTranslation = translationX;
-            iconState.visibleState = StatusBarIconView.STATE_ICON;
-            translationX += iconState.iconAppearAmount * view.getWidth();
-            if (translationX > getLayoutEnd()) {
-                // we are overflowing it with this icon
-                overflowingIconIndex = i - 1;
-                lastTwoIconWidth = view.getWidth();
-                break;
+            boolean isAmbient = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
+                    && iconState.iconAppearAmount > 0.0f;
+            boolean noOverflowAfter = i == childCount - 1;
+            if (mOpenedAmount != 0.0f) {
+                noOverflowAfter = noOverflowAfter && !hasAmbient;
             }
+            iconState.visibleState = StatusBarIconView.STATE_ICON;
+            if (firstOverflowIndex == -1 && (isAmbient
+                    || (translationX >= (noOverflowAfter ? layoutEnd - mIconSize : overflowStart)))) {
+                firstOverflowIndex = noOverflowAfter ? i - 1 : i;
+                int totalDotLength = mStaticDotRadius * 6 + 2 * mDotPadding;
+                visualOverflowStart = overflowStart + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT)
+                        - totalDotLength / 2
+                        - mIconSize * 0.5f + mStaticDotRadius;
+                if (isAmbient) {
+                    visualOverflowStart = Math.min(translationX, visualOverflowStart
+                            + mStaticDotRadius * 2 + mDotPadding);
+                } else {
+                    visualOverflowStart += (translationX - overflowStart) / mIconSize
+                            * (mStaticDotRadius * 2 + mDotPadding);
+                }
+                if (mShowAllIcons) {
+                    // We want to perfectly position the overflow in the static state, such that
+                    // it's perfectly centered instead of measuring it from the end.
+                    mVisualOverflowAdaption = 0;
+                    if (firstOverflowIndex != -1) {
+                        View firstOverflowView = getChildAt(i);
+                        IconState overflowState = mIconStates.get(firstOverflowView);
+                        float totalAmount = layoutEnd - overflowState.xTranslation;
+                        float newPosition = overflowState.xTranslation + totalAmount / 2
+                                - totalDotLength / 2
+                                - mIconSize * 0.5f + mStaticDotRadius;
+                        mVisualOverflowAdaption = newPosition - visualOverflowStart;
+                        visualOverflowStart = newPosition;
+                    }
+                } else {
+                    visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount);
+                }
+            }
+            translationX += iconState.iconAppearAmount * view.getWidth();
         }
-        if (overflowingIconIndex != -1) {
+        if (firstOverflowIndex != -1) {
             int numDots = 1;
-            View overflowIcon = getChildAt(overflowingIconIndex);
-            IconState overflowState = mIconStates.get(overflowIcon);
-            lastTwoIconWidth += overflowIcon.getWidth();
-            int dotWidth = mStaticDotRadius * 2 + mDotPadding;
-            int totalDotLength = mStaticDotRadius * 6 + 2 * mDotPadding;
-            translationX = (getLayoutEnd() - lastTwoIconWidth / 2 - totalDotLength / 2)
-                    - overflowIcon.getWidth() * 0.3f + mStaticDotRadius;
-            float overflowStart = getLayoutEnd() - lastTwoIconWidth;
-            float overlapAmount = (overflowState.xTranslation - overflowStart)
-                    / overflowIcon.getWidth();
-            translationX += overlapAmount * dotWidth;
-            for (int i = overflowingIconIndex; i < childCount; i++) {
+            translationX = visualOverflowStart;
+            for (int i = firstOverflowIndex; i < childCount; i++) {
                 View view = getChildAt(i);
                 IconState iconState = mIconStates.get(view);
+                int dotWidth = mStaticDotRadius * 2 + mDotPadding;
                 iconState.xTranslation = translationX;
                 if (numDots <= 3) {
-                    iconState.visibleState = StatusBarIconView.STATE_DOT;
-                    translationX += numDots == 3 ? 3 * dotWidth : dotWidth;
+                    if (numDots == 1 && iconState.iconAppearAmount < 0.8f) {
+                        iconState.visibleState = StatusBarIconView.STATE_ICON;
+                        numDots--;
+                    } else {
+                        iconState.visibleState = StatusBarIconView.STATE_DOT;
+                    }
+                    translationX += (numDots == 3 ? 3 * dotWidth : dotWidth)
+                            * iconState.iconAppearAmount;
                 } else {
                     iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
                 }
@@ -250,14 +319,14 @@
     }
 
     private float getActualPaddingEnd() {
-        if (mActualPaddingEnd < 0) {
+        if (mActualPaddingEnd == NO_VALUE) {
             return getPaddingEnd();
         }
         return mActualPaddingEnd;
     }
 
     private float getActualPaddingStart() {
-        if (mActualPaddingStart < 0) {
+        if (mActualPaddingStart == NO_VALUE) {
             return getPaddingStart();
         }
         return mActualPaddingStart;
@@ -295,7 +364,7 @@
     }
 
     public int getActualWidth() {
-        if (mActualLayoutWidth < 0) {
+        if (mActualLayoutWidth == NO_VALUE) {
             return getWidth();
         }
         return mActualLayoutWidth;
@@ -305,28 +374,90 @@
         mChangingViewPositions = changingViewPositions;
     }
 
+    public IconState getIconState(StatusBarIconView icon) {
+        return mIconStates.get(icon);
+    }
+
+    public void setSpeedBumpIndex(int speedBumpIndex) {
+        mSpeedBumpIndex = speedBumpIndex;
+    }
+
+    public void setOpenedAmount(float expandAmount) {
+        mOpenedAmount = expandAmount;
+    }
+
+    public float getVisualOverflowAdaption() {
+        return mVisualOverflowAdaption;
+    }
+
+    public void setVisualOverflowAdaption(float visualOverflowAdaption) {
+        mVisualOverflowAdaption = visualOverflowAdaption;
+    }
+
+    public boolean hasOverflow() {
+        float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
+        return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
+    }
+
+    public int getIconSize() {
+        return mIconSize;
+    }
+
     public class IconState extends ViewState {
         public float iconAppearAmount = 1.0f;
+        public float clampedAppearAmount = 1.0f;
         public int visibleState;
         public boolean justAdded = true;
+        public boolean needsCannedAnimation;
+        public boolean keepClampedPosition;
+        public boolean useFullTransitionAmount;
+        public boolean translateContent;
 
         @Override
         public void applyToView(View view) {
             if (view instanceof StatusBarIconView) {
                 StatusBarIconView icon = (StatusBarIconView) view;
-                AnimationProperties animationProperties = DOT_ANIMATION_PROPERTIES;
+                boolean animate = false;
+                AnimationProperties animationProperties = null;
                 if (justAdded) {
                     super.applyToView(icon);
                     icon.setAlpha(0.0f);
                     icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, false /* animate */);
                     animationProperties = ADD_ICON_PROPERTIES;
+                    animate = true;
+                } else if (visibleState != icon.getVisibleState()) {
+                    animationProperties = DOT_ANIMATION_PROPERTIES;
+                    animate = true;
                 }
-                boolean animate = visibleState != icon.getVisibleState() || justAdded;
-                if (!animate && mAnimationStartIndex >= 0
+                if (!animate && mAddAnimationStartIndex >= 0
+                        && indexOfChild(view) >= mAddAnimationStartIndex
                         && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
                             || visibleState != StatusBarIconView.STATE_HIDDEN)) {
-                    int viewIndex = indexOfChild(view);
-                    animate = viewIndex >= mAnimationStartIndex;
+                    animationProperties = DOT_ANIMATION_PROPERTIES;
+                    animate = true;
+                }
+                if (needsCannedAnimation) {
+                    AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
+                    animationFilter.reset();
+                    animationFilter.combineFilter(ICON_ANIMATION_PROPERTIES.getAnimationFilter());
+                    if (animationProperties != null) {
+                        animationFilter.combineFilter(animationProperties.getAnimationFilter());
+                    }
+                    animationProperties = mTempProperties;
+                    animationProperties.setDuration(CANNED_ANIMATION_DURATION);
+                    animate = true;
+                    mCannedAnimationStartIndex = indexOfChild(view);
+                }
+                if (!animate && mCannedAnimationStartIndex >= 0
+                        && indexOfChild(view) > mCannedAnimationStartIndex
+                        && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
+                        || visibleState != StatusBarIconView.STATE_HIDDEN)) {
+                    AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
+                    animationFilter.reset();
+                    animationFilter.animateX();
+                    animationProperties = mTempProperties;
+                    animationProperties.setDuration(CANNED_ANIMATION_DURATION);
+                    animate = true;
                 }
                 icon.setVisibleState(visibleState);
                 if (animate) {
@@ -336,6 +467,13 @@
                 }
             }
             justAdded = false;
+            needsCannedAnimation = false;
+        }
+
+        protected void onYTranslationAnimationFinished(View view) {
+            if (hidden) {
+                view.setVisibility(INVISIBLE);
+            }
         }
     }
 }
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 99e98f2e..b5865db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -208,6 +208,7 @@
     };
     private NotificationGroupManager mGroupManager;
     private boolean mOpening;
+    private int mIndicationBottomPadding;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -273,6 +274,8 @@
                 R.dimen.notification_panel_min_side_margin);
         mMaxFadeoutHeight = getResources().getDimensionPixelSize(
                 R.dimen.max_notification_fadeout_height);
+        mIndicationBottomPadding = getResources().getDimensionPixelSize(
+                R.dimen.keyguard_indication_bottom_padding);
     }
 
     public void updateResources() {
@@ -406,7 +409,8 @@
                 R.dimen.notification_divider_height));
         float shelfSize = mNotificationStackScroller.getNotificationShelf().getIntrinsicHeight()
                 + notificationPadding;
-        float availableSpace = mNotificationStackScroller.getHeight() - minPadding - shelfSize;
+        float availableSpace = mNotificationStackScroller.getHeight() - minPadding - shelfSize
+                - mIndicationBottomPadding;
         int count = 0;
         for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
             ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index f16c834..75cabe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -685,7 +685,7 @@
         mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
         ValueAnimator animator = createHeightAnimator(target);
         if (expand) {
-            if (expandBecauseOfFalsing) {
+            if (expandBecauseOfFalsing && vel < 0) {
                 vel = 0;
             }
             mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 2b74c847..b2dae50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -22,6 +22,7 @@
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
+
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -83,8 +84,8 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
@@ -123,13 +124,13 @@
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.LatencyTracker;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.DemoMode;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.Interpolators;
-import com.android.keyguard.LatencyTracker;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
@@ -178,19 +179,20 @@
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.statusbar.policy.CastControllerImpl;
 import com.android.systemui.statusbar.policy.EncryptionHelper;
-import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.LocationControllerImpl;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
 import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
 import com.android.systemui.statusbar.policy.PreviewInflater;
 import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
 import com.android.systemui.statusbar.policy.SecurityControllerImpl;
-import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -319,15 +321,15 @@
     NetworkControllerImpl mNetworkController;
     HotspotControllerImpl mHotspotController;
     RotationLockControllerImpl mRotationLockController;
-    UserInfoController mUserInfoController;
+    UserInfoControllerImpl mUserInfoController;
     protected ZenModeController mZenModeController;
     CastControllerImpl mCastController;
     VolumeComponent mVolumeComponent;
     KeyguardUserSwitcher mKeyguardUserSwitcher;
-    FlashlightController mFlashlightController;
+    FlashlightControllerImpl mFlashlightController;
     protected UserSwitcherController mUserSwitcherController;
-    NextAlarmController mNextAlarmController;
-    protected KeyguardMonitor mKeyguardMonitor;
+    NextAlarmControllerImpl mNextAlarmController;
+    protected KeyguardMonitorImpl mKeyguardMonitor;
     BrightnessMirrorController mBrightnessMirrorController;
     AccessibilityController mAccessibilityController;
     protected FingerprintUnlockController mFingerprintUnlockController;
@@ -895,7 +897,7 @@
         if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
             mRotationLockController = new RotationLockControllerImpl(mContext);
         }
-        mUserInfoController = new UserInfoController(mContext);
+        mUserInfoController = new UserInfoControllerImpl(mContext);
         mVolumeComponent = getComponent(VolumeComponent.class);
         if (mVolumeComponent != null) {
             mZenModeController = mVolumeComponent.getZenController();
@@ -906,16 +908,16 @@
         initSignalCluster(mKeyguardStatusBar);
         initEmergencyCryptkeeperText();
 
-        mFlashlightController = new FlashlightController(mContext);
+        mFlashlightController = new FlashlightControllerImpl(mContext);
         mKeyguardBottomArea.setFlashlightController(mFlashlightController);
         mKeyguardBottomArea.setPhoneStatusBar(this);
         mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
         mAccessibilityController = new AccessibilityController(mContext);
         mKeyguardBottomArea.setAccessibilityController(mAccessibilityController);
-        mNextAlarmController = new NextAlarmController(mContext);
+        mNextAlarmController = new NextAlarmControllerImpl(mContext);
         mLightStatusBarController = new LightStatusBarController(mIconController,
                 mBatteryController);
-        mKeyguardMonitor = new KeyguardMonitor(mContext);
+        mKeyguardMonitor = new KeyguardMonitorImpl(mContext);
             mUserSwitcherController = createUserSwitcherController();
         if (UserManager.get(mContext).isUserSwitcherEnabled()) {
             createUserSwitcher();
@@ -3572,10 +3574,18 @@
             final boolean dismissShade,
             final boolean afterKeyguardGone,
             final boolean deferred) {
-        dismissKeyguardThenExecute(() -> {
+        final Runnable dismissAction = () -> {
             if (runnable != null) {
                 AsyncTask.execute(runnable);
             }
+        };
+        dismissKeyguardThenExecute(() -> {
+            if (mStatusBarKeyguardViewManager.isShowing()
+                    && mStatusBarKeyguardViewManager.isOccluded()) {
+                mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+            } else {
+                dismissAction.run();
+            }
             if (dismissShade) {
                 if (mExpandedVisible) {
                     animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
@@ -3664,8 +3674,6 @@
 
     private void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
             boolean afterKeyguardGone) {
-        afterKeyguardGone |= mStatusBarKeyguardViewManager.isShowing()
-                && mStatusBarKeyguardViewManager.isOccluded();
         if (mStatusBarKeyguardViewManager.isShowing()) {
             mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
                     afterKeyguardGone);
@@ -4559,6 +4567,7 @@
         mGroupManager.setStatusBarState(state);
         mFalsingManager.setStatusBarState(state);
         mStatusBarWindowManager.setStatusBarState(state);
+        mStackScroller.setStatusBarState(state);
         updateReportRejectedTouchVisibility();
         updateDozing();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 8fd6bbf..567ab3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -143,7 +143,7 @@
         mBattery = battery;
         mIconController = iconController;
         mNextAlarmController = nextAlarmController;
-        mProfileController = new ManagedProfileController(this);
+        mProfileController = new ManagedProfileControllerImpl(this);
 
         mHandlerThread = new HandlerThread(QSTileHost.class.getSimpleName(),
                 Process.THREAD_PRIORITY_BACKGROUND);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 28aed87..9ab4d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -236,10 +236,17 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mHost.getUserInfoController().addCallback(this);
+    }
+
+    @Override
     protected void onDetachedFromWindow() {
         setListening(false);
         mHost.getUserInfoController().removeCallback(this);
         mHost.getNetworkController().removeEmergencyListener(this);
+        mHost.getUserInfoController().removeCallback(this);
         super.onDetachedFromWindow();
     }
 
@@ -368,7 +375,7 @@
     }
 
     public void setUserInfoController(UserInfoController userInfoController) {
-        userInfoController.addCallback(this);
+        // Don't care
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 0e74e57..749ff99e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -556,10 +556,6 @@
         mScrimBehind.setExcludedArea(area);
     }
 
-    public void setLeftInset(int inset) {
-        mScrimBehind.setLeftInset(inset);
-    }
-
     public int getScrimBehindColor() {
         return mScrimBehind.getScrimColorWithAlpha();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 4263670..2e279b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -40,6 +40,8 @@
 import static com.android.keyguard.KeyguardHostView.OnDismissAction;
 import static com.android.systemui.statusbar.phone.FingerprintUnlockController.*;
 
+import java.util.ArrayList;
+
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
  * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -90,6 +92,7 @@
     protected boolean mLastRemoteInputActive;
 
     private OnDismissAction mAfterKeyguardGoneAction;
+    private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
     private boolean mDeviceWillWakeUp;
     private boolean mDeferScrimFadeOut;
 
@@ -165,6 +168,13 @@
     }
 
     /**
+     * Adds a {@param runnable} to be executed after Keyguard is gone.
+     */
+    public void addAfterKeyguardGoneRunnable(Runnable runnable) {
+        mAfterKeyguardGoneRunnables.add(runnable);
+    }
+
+    /**
      * Reset the state of the view.
      */
     public void reset(boolean hideBouncerWhenShowing) {
@@ -418,6 +428,10 @@
             mAfterKeyguardGoneAction.onDismiss();
             mAfterKeyguardGoneAction = null;
         }
+        for (int i = 0; i < mAfterKeyguardGoneRunnables.size(); i++) {
+            mAfterKeyguardGoneRunnables.get(i).run();
+        }
+        mAfterKeyguardGoneRunnables.clear();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index f6dd88d9..487f0e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -130,7 +130,6 @@
     }
 
     private void applyMargins() {
-        mService.mScrimController.setLeftInset(mLeftInset);
         final int N = getChildCount();
         for (int i = 0; i < N; i++) {
             View child = getChildAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
index e5f1e68..0df7859 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
@@ -14,92 +14,14 @@
 
 package com.android.systemui.statusbar.policy;
 
-import android.content.Context;
-import android.net.INetworkPolicyListener;
-import android.net.NetworkPolicyManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-
 import com.android.systemui.statusbar.policy.DataSaverController.Listener;
 
-import java.util.ArrayList;
+public interface DataSaverController extends CallbackController<Listener> {
 
-public class DataSaverController implements CallbackController<Listener> {
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-    private final ArrayList<Listener> mListeners = new ArrayList<>();
-    private final NetworkPolicyManager mPolicyManager;
-
-    public DataSaverController(Context context) {
-        mPolicyManager = NetworkPolicyManager.from(context);
-    }
-
-    private void handleRestrictBackgroundChanged(boolean isDataSaving) {
-        synchronized (mListeners) {
-            for (int i = 0; i < mListeners.size(); i++) {
-                mListeners.get(i).onDataSaverChanged(isDataSaving);
-            }
-        }
-    }
-
-    public void addCallback(Listener listener) {
-        synchronized (mListeners) {
-            mListeners.add(listener);
-            if (mListeners.size() == 1) {
-                mPolicyManager.registerListener(mPolicyListener);
-            }
-        }
-        listener.onDataSaverChanged(isDataSaverEnabled());
-    }
-
-    public void removeCallback(Listener listener) {
-        synchronized (mListeners) {
-            mListeners.remove(listener);
-            if (mListeners.size() == 0) {
-                mPolicyManager.unregisterListener(mPolicyListener);
-            }
-        }
-    }
-
-    public boolean isDataSaverEnabled() {
-        return mPolicyManager.getRestrictBackground();
-    }
-
-    public void setDataSaverEnabled(boolean enabled) {
-        mPolicyManager.setRestrictBackground(enabled);
-        try {
-            mPolicyListener.onRestrictBackgroundChanged(enabled);
-        } catch (RemoteException e) {
-        }
-    }
-
-    private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
-        @Override
-        public void onUidRulesChanged(int uid, int uidRules) throws RemoteException {
-        }
-
-        @Override
-        public void onMeteredIfacesChanged(String[] strings) throws RemoteException {
-        }
-
-        @Override
-        public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    handleRestrictBackgroundChanged(isDataSaving);
-                }
-            });
-        }
-
-        @Override
-        public void onUidPoliciesChanged(int uid, int uidPolicies) throws RemoteException {
-        }
-    };
+    boolean isDataSaverEnabled();
+    void setDataSaverEnabled(boolean enabled);
 
     public interface Listener {
         void onDataSaverChanged(boolean isDataSaving);
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
new file mode 100644
index 0000000..2951943
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.net.INetworkPolicyListener;
+import android.net.NetworkPolicyManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+
+import com.android.systemui.statusbar.policy.DataSaverController.Listener;
+
+import java.util.ArrayList;
+
+public class DataSaverControllerImpl implements DataSaverController {
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final ArrayList<Listener> mListeners = new ArrayList<>();
+    private final NetworkPolicyManager mPolicyManager;
+
+    public DataSaverControllerImpl(Context context) {
+        mPolicyManager = NetworkPolicyManager.from(context);
+    }
+
+    private void handleRestrictBackgroundChanged(boolean isDataSaving) {
+        synchronized (mListeners) {
+            for (int i = 0; i < mListeners.size(); i++) {
+                mListeners.get(i).onDataSaverChanged(isDataSaving);
+            }
+        }
+    }
+
+    public void addCallback(Listener listener) {
+        synchronized (mListeners) {
+            mListeners.add(listener);
+            if (mListeners.size() == 1) {
+                mPolicyManager.registerListener(mPolicyListener);
+            }
+        }
+        listener.onDataSaverChanged(isDataSaverEnabled());
+    }
+
+    public void removeCallback(Listener listener) {
+        synchronized (mListeners) {
+            mListeners.remove(listener);
+            if (mListeners.size() == 0) {
+                mPolicyManager.unregisterListener(mPolicyListener);
+            }
+        }
+    }
+
+    public boolean isDataSaverEnabled() {
+        return mPolicyManager.getRestrictBackground();
+    }
+
+    public void setDataSaverEnabled(boolean enabled) {
+        mPolicyManager.setRestrictBackground(enabled);
+        try {
+            mPolicyListener.onRestrictBackgroundChanged(enabled);
+        } catch (RemoteException e) {
+        }
+    }
+
+    private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+        @Override
+        public void onUidRulesChanged(int uid, int uidRules) throws RemoteException {
+        }
+
+        @Override
+        public void onMeteredIfacesChanged(String[] strings) throws RemoteException {
+        }
+
+        @Override
+        public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    handleRestrictBackgroundChanged(isDataSaving);
+                }
+            });
+        }
+
+        @Override
+        public void onUidPoliciesChanged(int uid, int uidPolicies) throws RemoteException {
+        }
+    };
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index 8abfb89..a2d1baf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.policy;
 
 import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -41,12 +42,20 @@
 public class EmergencyCryptkeeperText extends TextView {
 
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+    private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
         @Override
         public void onPhoneStateChanged(int phoneState) {
             update();
         }
     };
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) {
+                update();
+            }
+        }
+    };
 
     public EmergencyCryptkeeperText(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -58,6 +67,8 @@
         super.onAttachedToWindow();
         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         mKeyguardUpdateMonitor.registerCallback(mCallback);
+        getContext().registerReceiver(mReceiver,
+                new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
         update();
     }
 
@@ -67,6 +78,7 @@
         if (mKeyguardUpdateMonitor != null) {
             mKeyguardUpdateMonitor.removeCallback(mCallback);
         }
+        getContext().unregisterReceiver(mReceiver);
     }
 
     public void update() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index 0f77b03..6023f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -1,255 +1,27 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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
+ * 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
+ * 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.systemui.statusbar.policy;
 
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.text.TextUtils;
-import android.util.Log;
-
 import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
+public interface FlashlightController extends CallbackController<FlashlightListener> {
 
-/**
- * Manages the flashlight.
- */
-public class FlashlightController implements CallbackController<FlashlightListener> {
-
-    private static final String TAG = "FlashlightController";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final int DISPATCH_ERROR = 0;
-    private static final int DISPATCH_CHANGED = 1;
-    private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
-
-    private final CameraManager mCameraManager;
-    private final Context mContext;
-    /** Call {@link #ensureHandler()} before using */
-    private Handler mHandler;
-
-    /** Lock on mListeners when accessing */
-    private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
-
-    /** Lock on {@code this} when accessing */
-    private boolean mFlashlightEnabled;
-
-    private String mCameraId;
-    private boolean mTorchAvailable;
-
-    public FlashlightController(Context context) {
-        mContext = context;
-        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
-
-        tryInitCamera();
-    }
-
-    private void tryInitCamera() {
-        try {
-            mCameraId = getCameraId();
-        } catch (Throwable e) {
-            Log.e(TAG, "Couldn't initialize.", e);
-            return;
-        }
-
-        if (mCameraId != null) {
-            ensureHandler();
-            mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
-        }
-    }
-
-    public void setFlashlight(boolean enabled) {
-        boolean pendingError = false;
-        synchronized (this) {
-            if (mCameraId == null) return;
-            if (mFlashlightEnabled != enabled) {
-                mFlashlightEnabled = enabled;
-                try {
-                    mCameraManager.setTorchMode(mCameraId, enabled);
-                } catch (CameraAccessException e) {
-                    Log.e(TAG, "Couldn't set torch mode", e);
-                    mFlashlightEnabled = false;
-                    pendingError = true;
-                }
-            }
-        }
-        dispatchModeChanged(mFlashlightEnabled);
-        if (pendingError) {
-            dispatchError();
-        }
-    }
-
-    public boolean hasFlashlight() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
-    }
-
-    public synchronized boolean isEnabled() {
-        return mFlashlightEnabled;
-    }
-
-    public synchronized boolean isAvailable() {
-        return mTorchAvailable;
-    }
-
-    public void addCallback(FlashlightListener l) {
-        synchronized (mListeners) {
-            if (mCameraId == null) {
-                tryInitCamera();
-            }
-            cleanUpListenersLocked(l);
-            mListeners.add(new WeakReference<>(l));
-        }
-    }
-
-    public void removeCallback(FlashlightListener l) {
-        synchronized (mListeners) {
-            cleanUpListenersLocked(l);
-        }
-    }
-
-    private synchronized void ensureHandler() {
-        if (mHandler == null) {
-            HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
-            thread.start();
-            mHandler = new Handler(thread.getLooper());
-        }
-    }
-
-    private String getCameraId() throws CameraAccessException {
-        String[] ids = mCameraManager.getCameraIdList();
-        for (String id : ids) {
-            CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
-            Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
-            Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
-            if (flashAvailable != null && flashAvailable
-                    && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
-                return id;
-            }
-        }
-        return null;
-    }
-
-    private void dispatchModeChanged(boolean enabled) {
-        dispatchListeners(DISPATCH_CHANGED, enabled);
-    }
-
-    private void dispatchError() {
-        dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */);
-    }
-
-    private void dispatchAvailabilityChanged(boolean available) {
-        dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available);
-    }
-
-    private void dispatchListeners(int message, boolean argument) {
-        synchronized (mListeners) {
-            final int N = mListeners.size();
-            boolean cleanup = false;
-            for (int i = 0; i < N; i++) {
-                FlashlightListener l = mListeners.get(i).get();
-                if (l != null) {
-                    if (message == DISPATCH_ERROR) {
-                        l.onFlashlightError();
-                    } else if (message == DISPATCH_CHANGED) {
-                        l.onFlashlightChanged(argument);
-                    } else if (message == DISPATCH_AVAILABILITY_CHANGED) {
-                        l.onFlashlightAvailabilityChanged(argument);
-                    }
-                } else {
-                    cleanup = true;
-                }
-            }
-            if (cleanup) {
-                cleanUpListenersLocked(null);
-            }
-        }
-    }
-
-    private void cleanUpListenersLocked(FlashlightListener listener) {
-        for (int i = mListeners.size() - 1; i >= 0; i--) {
-            FlashlightListener found = mListeners.get(i).get();
-            if (found == null || found == listener) {
-                mListeners.remove(i);
-            }
-        }
-    }
-
-    private final CameraManager.TorchCallback mTorchCallback =
-            new CameraManager.TorchCallback() {
-
-        @Override
-        public void onTorchModeUnavailable(String cameraId) {
-            if (TextUtils.equals(cameraId, mCameraId)) {
-                setCameraAvailable(false);
-            }
-        }
-
-        @Override
-        public void onTorchModeChanged(String cameraId, boolean enabled) {
-            if (TextUtils.equals(cameraId, mCameraId)) {
-                setCameraAvailable(true);
-                setTorchMode(enabled);
-            }
-        }
-
-        private void setCameraAvailable(boolean available) {
-            boolean changed;
-            synchronized (FlashlightController.this) {
-                changed = mTorchAvailable != available;
-                mTorchAvailable = available;
-            }
-            if (changed) {
-                if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
-                dispatchAvailabilityChanged(available);
-            }
-        }
-
-        private void setTorchMode(boolean enabled) {
-            boolean changed;
-            synchronized (FlashlightController.this) {
-                changed = mFlashlightEnabled != enabled;
-                mFlashlightEnabled = enabled;
-            }
-            if (changed) {
-                if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
-                dispatchModeChanged(enabled);
-            }
-        }
-    };
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("FlashlightController state:");
-
-        pw.print("  mCameraId=");
-        pw.println(mCameraId);
-        pw.print("  mFlashlightEnabled=");
-        pw.println(mFlashlightEnabled);
-        pw.print("  mTorchAvailable=");
-        pw.println(mTorchAvailable);
-    }
+    boolean hasFlashlight();
+    void setFlashlight(boolean newState);
+    boolean isAvailable();
+    boolean isEnabled();
 
     public interface FlashlightListener {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
new file mode 100644
index 0000000..008d837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Manages the flashlight.
+ */
+public class FlashlightControllerImpl implements FlashlightController {
+
+    private static final String TAG = "FlashlightController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final int DISPATCH_ERROR = 0;
+    private static final int DISPATCH_CHANGED = 1;
+    private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
+
+    private final CameraManager mCameraManager;
+    private final Context mContext;
+    /** Call {@link #ensureHandler()} before using */
+    private Handler mHandler;
+
+    /** Lock on mListeners when accessing */
+    private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
+
+    /** Lock on {@code this} when accessing */
+    private boolean mFlashlightEnabled;
+
+    private String mCameraId;
+    private boolean mTorchAvailable;
+
+    public FlashlightControllerImpl(Context context) {
+        mContext = context;
+        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+
+        tryInitCamera();
+    }
+
+    private void tryInitCamera() {
+        try {
+            mCameraId = getCameraId();
+        } catch (Throwable e) {
+            Log.e(TAG, "Couldn't initialize.", e);
+            return;
+        }
+
+        if (mCameraId != null) {
+            ensureHandler();
+            mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
+        }
+    }
+
+    public void setFlashlight(boolean enabled) {
+        boolean pendingError = false;
+        synchronized (this) {
+            if (mCameraId == null) return;
+            if (mFlashlightEnabled != enabled) {
+                mFlashlightEnabled = enabled;
+                try {
+                    mCameraManager.setTorchMode(mCameraId, enabled);
+                } catch (CameraAccessException e) {
+                    Log.e(TAG, "Couldn't set torch mode", e);
+                    mFlashlightEnabled = false;
+                    pendingError = true;
+                }
+            }
+        }
+        dispatchModeChanged(mFlashlightEnabled);
+        if (pendingError) {
+            dispatchError();
+        }
+    }
+
+    public boolean hasFlashlight() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+    }
+
+    public synchronized boolean isEnabled() {
+        return mFlashlightEnabled;
+    }
+
+    public synchronized boolean isAvailable() {
+        return mTorchAvailable;
+    }
+
+    public void addCallback(FlashlightListener l) {
+        synchronized (mListeners) {
+            if (mCameraId == null) {
+                tryInitCamera();
+            }
+            cleanUpListenersLocked(l);
+            mListeners.add(new WeakReference<>(l));
+        }
+    }
+
+    public void removeCallback(FlashlightListener l) {
+        synchronized (mListeners) {
+            cleanUpListenersLocked(l);
+        }
+    }
+
+    private synchronized void ensureHandler() {
+        if (mHandler == null) {
+            HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
+            thread.start();
+            mHandler = new Handler(thread.getLooper());
+        }
+    }
+
+    private String getCameraId() throws CameraAccessException {
+        String[] ids = mCameraManager.getCameraIdList();
+        for (String id : ids) {
+            CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
+            Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+            Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
+            if (flashAvailable != null && flashAvailable
+                    && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+                return id;
+            }
+        }
+        return null;
+    }
+
+    private void dispatchModeChanged(boolean enabled) {
+        dispatchListeners(DISPATCH_CHANGED, enabled);
+    }
+
+    private void dispatchError() {
+        dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */);
+    }
+
+    private void dispatchAvailabilityChanged(boolean available) {
+        dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available);
+    }
+
+    private void dispatchListeners(int message, boolean argument) {
+        synchronized (mListeners) {
+            final int N = mListeners.size();
+            boolean cleanup = false;
+            for (int i = 0; i < N; i++) {
+                FlashlightListener l = mListeners.get(i).get();
+                if (l != null) {
+                    if (message == DISPATCH_ERROR) {
+                        l.onFlashlightError();
+                    } else if (message == DISPATCH_CHANGED) {
+                        l.onFlashlightChanged(argument);
+                    } else if (message == DISPATCH_AVAILABILITY_CHANGED) {
+                        l.onFlashlightAvailabilityChanged(argument);
+                    }
+                } else {
+                    cleanup = true;
+                }
+            }
+            if (cleanup) {
+                cleanUpListenersLocked(null);
+            }
+        }
+    }
+
+    private void cleanUpListenersLocked(FlashlightListener listener) {
+        for (int i = mListeners.size() - 1; i >= 0; i--) {
+            FlashlightListener found = mListeners.get(i).get();
+            if (found == null || found == listener) {
+                mListeners.remove(i);
+            }
+        }
+    }
+
+    private final CameraManager.TorchCallback mTorchCallback =
+            new CameraManager.TorchCallback() {
+
+        @Override
+        public void onTorchModeUnavailable(String cameraId) {
+            if (TextUtils.equals(cameraId, mCameraId)) {
+                setCameraAvailable(false);
+            }
+        }
+
+        @Override
+        public void onTorchModeChanged(String cameraId, boolean enabled) {
+            if (TextUtils.equals(cameraId, mCameraId)) {
+                setCameraAvailable(true);
+                setTorchMode(enabled);
+            }
+        }
+
+        private void setCameraAvailable(boolean available) {
+            boolean changed;
+            synchronized (FlashlightControllerImpl.this) {
+                changed = mTorchAvailable != available;
+                mTorchAvailable = available;
+            }
+            if (changed) {
+                if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
+                dispatchAvailabilityChanged(available);
+            }
+        }
+
+        private void setTorchMode(boolean enabled) {
+            boolean changed;
+            synchronized (FlashlightControllerImpl.this) {
+                changed = mFlashlightEnabled != enabled;
+                mFlashlightEnabled = enabled;
+            }
+            if (changed) {
+                if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
+                dispatchModeChanged(enabled);
+            }
+        }
+    };
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("FlashlightController state:");
+
+        pw.print("  mCameraId=");
+        pw.println(mCameraId);
+        pw.print("  mFlashlightEnabled=");
+        pw.println(mFlashlightEnabled);
+        pw.print("  mTorchAvailable=");
+        pw.println(mTorchAvailable);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index 0396613..de47267 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -1,126 +1,28 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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
+ * 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.
+ * 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.systemui.statusbar.policy;
 
-import android.app.ActivityManager;
-import android.content.Context;
-import android.os.RemoteException;
-import android.view.WindowManagerGlobal;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
 
-import java.util.ArrayList;
+public interface KeyguardMonitor extends CallbackController<Callback> {
 
-public class KeyguardMonitor extends KeyguardUpdateMonitorCallback
-        implements CallbackController<Callback> {
-
-    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
-
-    private final Context mContext;
-    private final CurrentUserTracker mUserTracker;
-    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
-    private int mCurrentUser;
-    private boolean mShowing;
-    private boolean mSecure;
-    private boolean mOccluded;
-    private boolean mCanSkipBouncer;
-
-    private boolean mListening;
-
-    public KeyguardMonitor(Context context) {
-        mContext = context;
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-        mUserTracker = new CurrentUserTracker(mContext) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mCurrentUser = newUserId;
-                updateCanSkipBouncerState();
-            }
-        };
-    }
-
-    public void addCallback(Callback callback) {
-        mCallbacks.add(callback);
-        if (mCallbacks.size() != 0 && !mListening) {
-            mListening = true;
-            mCurrentUser = ActivityManager.getCurrentUser();
-            updateCanSkipBouncerState();
-            mKeyguardUpdateMonitor.registerCallback(this);
-            mUserTracker.startTracking();
-        }
-    }
-
-    public void removeCallback(Callback callback) {
-        if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
-            mListening = false;
-            mKeyguardUpdateMonitor.removeCallback(this);
-            mUserTracker.stopTracking();
-        }
-    }
-
-    public boolean isShowing() {
-        return mShowing;
-    }
-
-    public boolean isSecure() {
-        return mSecure;
-    }
-
-    public boolean isOccluded() {
-        return mOccluded;
-    }
-
-    public boolean canSkipBouncer() {
-        return mCanSkipBouncer;
-    }
-
-    public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
-        if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
-        mShowing = showing;
-        mSecure = secure;
-        mOccluded = occluded;
-        notifyKeyguardChanged();
-    }
-
-    @Override
-    public void onTrustChanged(int userId) {
-        updateCanSkipBouncerState();
-        notifyKeyguardChanged();
-    }
-
-    public boolean isDeviceInteractive() {
-        return mKeyguardUpdateMonitor.isDeviceInteractive();
-    }
-
-    private void updateCanSkipBouncerState() {
-        mCanSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(mCurrentUser);
-    }
-
-    private void notifyKeyguardChanged() {
-        for (Callback callback : mCallbacks) {
-            callback.onKeyguardChanged();
-        }
-    }
+    boolean isSecure();
+    boolean canSkipBouncer();
+    boolean isShowing();
 
     public interface Callback {
         void onKeyguardChanged();
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
new file mode 100644
index 0000000..769f93f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.policy;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.view.WindowManagerGlobal;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
+
+import java.util.ArrayList;
+
+public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
+        implements KeyguardMonitor {
+
+    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+
+    private final Context mContext;
+    private final CurrentUserTracker mUserTracker;
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+    private int mCurrentUser;
+    private boolean mShowing;
+    private boolean mSecure;
+    private boolean mOccluded;
+    private boolean mCanSkipBouncer;
+
+    private boolean mListening;
+
+    public KeyguardMonitorImpl(Context context) {
+        mContext = context;
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mUserTracker = new CurrentUserTracker(mContext) {
+            @Override
+            public void onUserSwitched(int newUserId) {
+                mCurrentUser = newUserId;
+                updateCanSkipBouncerState();
+            }
+        };
+    }
+
+    public void addCallback(Callback callback) {
+        mCallbacks.add(callback);
+        if (mCallbacks.size() != 0 && !mListening) {
+            mListening = true;
+            mCurrentUser = ActivityManager.getCurrentUser();
+            updateCanSkipBouncerState();
+            mKeyguardUpdateMonitor.registerCallback(this);
+            mUserTracker.startTracking();
+        }
+    }
+
+    public void removeCallback(Callback callback) {
+        if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
+            mListening = false;
+            mKeyguardUpdateMonitor.removeCallback(this);
+            mUserTracker.stopTracking();
+        }
+    }
+
+    public boolean isShowing() {
+        return mShowing;
+    }
+
+    public boolean isSecure() {
+        return mSecure;
+    }
+
+    public boolean isOccluded() {
+        return mOccluded;
+    }
+
+    public boolean canSkipBouncer() {
+        return mCanSkipBouncer;
+    }
+
+    public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
+        if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
+        mShowing = showing;
+        mSecure = secure;
+        mOccluded = occluded;
+        notifyKeyguardChanged();
+    }
+
+    @Override
+    public void onTrustChanged(int userId) {
+        updateCanSkipBouncerState();
+        notifyKeyguardChanged();
+    }
+
+    public boolean isDeviceInteractive() {
+        return mKeyguardUpdateMonitor.isDeviceInteractive();
+    }
+
+    private void updateCanSkipBouncerState() {
+        mCanSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(mCurrentUser);
+    }
+
+    private void notifyKeyguardChanged() {
+        for (Callback callback : mCallbacks) {
+            callback.onKeyguardChanged();
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 1a9756f..a7fab41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -159,7 +159,7 @@
         mConfig = config;
         mReceiverHandler = new Handler(bgLooper);
         mCallbackHandler = callbackHandler;
-        mDataSaverController = new DataSaverController(context);
+        mDataSaverController = new DataSaverControllerImpl(context);
 
         mSubscriptionManager = subManager;
         mSubDefaults = defaultsHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
index 28935bf..e5b0c03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
@@ -1,84 +1,24 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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
+ * 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
+ * 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.systemui.statusbar.policy;
 
 import android.app.AlarmManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
 
 import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-public class NextAlarmController extends BroadcastReceiver
-        implements CallbackController<NextAlarmChangeCallback> {
-
-    private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>();
-
-    private AlarmManager mAlarmManager;
-    private AlarmManager.AlarmClockInfo mNextAlarm;
-
-    public NextAlarmController(Context context) {
-        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
-        context.registerReceiverAsUser(this, UserHandle.ALL, filter, null, null);
-        updateNextAlarm();
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("NextAlarmController state:");
-        pw.print("  mNextAlarm="); pw.println(mNextAlarm);
-    }
-
-    public void addCallback(NextAlarmChangeCallback cb) {
-        mChangeCallbacks.add(cb);
-        cb.onNextAlarmChanged(mNextAlarm);
-    }
-
-    public void removeCallback(NextAlarmChangeCallback cb) {
-        mChangeCallbacks.remove(cb);
-    }
-
-    public void onReceive(Context context, Intent intent) {
-        final String action = intent.getAction();
-        if (action.equals(Intent.ACTION_USER_SWITCHED)
-                || action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
-            updateNextAlarm();
-        }
-    }
-
-    private void updateNextAlarm() {
-        mNextAlarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
-        fireNextAlarmChanged();
-    }
-
-    private void fireNextAlarmChanged() {
-        int n = mChangeCallbacks.size();
-        for (int i = 0; i < n; i++) {
-            mChangeCallbacks.get(i).onNextAlarmChanged(mNextAlarm);
-        }
-    }
+public interface NextAlarmController extends CallbackController<NextAlarmChangeCallback> {
 
     public interface NextAlarmChangeCallback {
         void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
new file mode 100644
index 0000000..dfdeae1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.policy;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class NextAlarmControllerImpl extends BroadcastReceiver
+        implements NextAlarmController {
+
+    private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>();
+
+    private AlarmManager mAlarmManager;
+    private AlarmManager.AlarmClockInfo mNextAlarm;
+
+    public NextAlarmControllerImpl(Context context) {
+        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+        context.registerReceiverAsUser(this, UserHandle.ALL, filter, null, null);
+        updateNextAlarm();
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("NextAlarmController state:");
+        pw.print("  mNextAlarm="); pw.println(mNextAlarm);
+    }
+
+    public void addCallback(NextAlarmChangeCallback cb) {
+        mChangeCallbacks.add(cb);
+        cb.onNextAlarmChanged(mNextAlarm);
+    }
+
+    public void removeCallback(NextAlarmChangeCallback cb) {
+        mChangeCallbacks.remove(cb);
+    }
+
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (action.equals(Intent.ACTION_USER_SWITCHED)
+                || action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
+            updateNextAlarm();
+        }
+    }
+
+    private void updateNextAlarm() {
+        mNextAlarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+        fireNextAlarmChanged();
+    }
+
+    private void fireNextAlarmChanged() {
+        int n = mChangeCallbacks.size();
+        for (int i = 0; i < n; i++) {
+            mChangeCallbacks.get(i).onNextAlarmChanged(mNextAlarm);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
index c09747b..1e23a20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
@@ -1,233 +1,28 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 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
+ * 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
+ * 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.systemui.statusbar.policy;
 
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.ContactsContract;
-import android.util.Log;
 
-import com.android.internal.util.UserIcons;
-import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
 
-import java.util.ArrayList;
+public interface UserInfoController extends CallbackController<OnUserInfoChangedListener> {
 
-public class UserInfoController implements CallbackController<OnUserInfoChangedListener> {
-
-    private static final String TAG = "UserInfoController";
-
-    private final Context mContext;
-    private final ArrayList<OnUserInfoChangedListener> mCallbacks =
-            new ArrayList<OnUserInfoChangedListener>();
-    private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
-
-    private String mUserName;
-    private Drawable mUserDrawable;
-    private String mUserAccount;
-
-    public UserInfoController(Context context) {
-        mContext = context;
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mContext.registerReceiver(mReceiver, filter);
-
-        IntentFilter profileFilter = new IntentFilter();
-        profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
-        profileFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
-        mContext.registerReceiverAsUser(mProfileReceiver, UserHandle.ALL, profileFilter,
-                null, null);
-    }
-
-    public void addCallback(OnUserInfoChangedListener callback) {
-        mCallbacks.add(callback);
-        callback.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
-    }
-
-    public void removeCallback(OnUserInfoChangedListener callback) {
-        mCallbacks.remove(callback);
-    }
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                reloadUserInfo();
-            }
-        }
-    };
-
-    private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
-                    Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
-                try {
-                    final int currentUser = ActivityManager.getService().getCurrentUser().id;
-                    final int changedUser =
-                            intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
-                    if (changedUser == currentUser) {
-                        reloadUserInfo();
-                    }
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Couldn't get current user id for profile change", e);
-                }
-            }
-        }
-    };
-
-    public void reloadUserInfo() {
-        if (mUserInfoTask != null) {
-            mUserInfoTask.cancel(false);
-            mUserInfoTask = null;
-        }
-        queryForUserInformation();
-    }
-
-    private void queryForUserInformation() {
-        Context currentUserContext;
-        UserInfo userInfo;
-        try {
-            userInfo = ActivityManager.getService().getCurrentUser();
-            currentUserContext = mContext.createPackageContextAsUser("android", 0,
-                    new UserHandle(userInfo.id));
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Couldn't create user context", e);
-            throw new RuntimeException(e);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't get user info", e);
-            throw new RuntimeException(e);
-        }
-        final int userId = userInfo.id;
-        final boolean isGuest = userInfo.isGuest();
-        final String userName = userInfo.name;
-
-        final Resources res = mContext.getResources();
-        final int avatarSize = Math.max(
-                res.getDimensionPixelSize(R.dimen.multi_user_avatar_expanded_size),
-                res.getDimensionPixelSize(R.dimen.multi_user_avatar_keyguard_size));
-
-        final Context context = currentUserContext;
-        mUserInfoTask = new AsyncTask<Void, Void, UserInfoQueryResult>() {
-
-            @Override
-            protected UserInfoQueryResult doInBackground(Void... params) {
-                final UserManager um = UserManager.get(mContext);
-
-                // Fall back to the UserManager nickname if we can't read the name from the local
-                // profile below.
-                String name = userName;
-                Drawable avatar = null;
-                Bitmap rawAvatar = um.getUserIcon(userId);
-                if (rawAvatar != null) {
-                    avatar = new UserIconDrawable(avatarSize)
-                            .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
-                } else {
-                    avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
-                            /* light= */ true);
-                }
-
-                // If it's a single-user device, get the profile name, since the nickname is not
-                // usually valid
-                if (um.getUsers().size() <= 1) {
-                    // Try and read the display name from the local profile
-                    final Cursor cursor = context.getContentResolver().query(
-                            ContactsContract.Profile.CONTENT_URI, new String[] {
-                                    ContactsContract.CommonDataKinds.Phone._ID,
-                                    ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
-                            }, null, null, null);
-                    if (cursor != null) {
-                        try {
-                            if (cursor.moveToFirst()) {
-                                name = cursor.getString(cursor.getColumnIndex(
-                                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
-                            }
-                        } finally {
-                            cursor.close();
-                        }
-                    }
-                }
-                String userAccount = um.getUserAccount(userId);
-                return new UserInfoQueryResult(name, avatar, userAccount);
-            }
-
-            @Override
-            protected void onPostExecute(UserInfoQueryResult result) {
-                mUserName = result.getName();
-                mUserDrawable = result.getAvatar();
-                mUserAccount = result.getUserAccount();
-                mUserInfoTask = null;
-                notifyChanged();
-            }
-        };
-        mUserInfoTask.execute();
-    }
-
-    private void notifyChanged() {
-        for (OnUserInfoChangedListener listener : mCallbacks) {
-            listener.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
-        }
-    }
-
-    public void onDensityOrFontScaleChanged() {
-        reloadUserInfo();
-    }
+    void reloadUserInfo();
 
     public interface OnUserInfoChangedListener {
         public void onUserInfoChanged(String name, Drawable picture, String userAccount);
     }
-
-    private static class UserInfoQueryResult {
-        private String mName;
-        private Drawable mAvatar;
-        private String mUserAccount;
-
-        public UserInfoQueryResult(String name, Drawable avatar, String userAccount) {
-            mName = name;
-            mAvatar = avatar;
-            mUserAccount = userAccount;
-        }
-
-        public String getName() {
-            return mName;
-        }
-
-        public Drawable getAvatar() {
-            return mAvatar;
-        }
-
-        public String getUserAccount() {
-            return mUserAccount;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
new file mode 100644
index 0000000..b1e4b03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2014 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.systemui.statusbar.policy;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.ContactsContract;
+import android.util.Log;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.drawable.UserIconDrawable;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
+
+import java.util.ArrayList;
+
+public class UserInfoControllerImpl implements UserInfoController {
+
+    private static final String TAG = "UserInfoController";
+
+    private final Context mContext;
+    private final ArrayList<OnUserInfoChangedListener> mCallbacks =
+            new ArrayList<OnUserInfoChangedListener>();
+    private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
+
+    private String mUserName;
+    private Drawable mUserDrawable;
+    private String mUserAccount;
+
+    public UserInfoControllerImpl(Context context) {
+        mContext = context;
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        mContext.registerReceiver(mReceiver, filter);
+
+        IntentFilter profileFilter = new IntentFilter();
+        profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
+        profileFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
+        mContext.registerReceiverAsUser(mProfileReceiver, UserHandle.ALL, profileFilter,
+                null, null);
+    }
+
+    public void addCallback(OnUserInfoChangedListener callback) {
+        mCallbacks.add(callback);
+        callback.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
+    }
+
+    public void removeCallback(OnUserInfoChangedListener callback) {
+        mCallbacks.remove(callback);
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                reloadUserInfo();
+            }
+        }
+    };
+
+    private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
+                    Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
+                try {
+                    final int currentUser = ActivityManager.getService().getCurrentUser().id;
+                    final int changedUser =
+                            intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+                    if (changedUser == currentUser) {
+                        reloadUserInfo();
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Couldn't get current user id for profile change", e);
+                }
+            }
+        }
+    };
+
+    public void reloadUserInfo() {
+        if (mUserInfoTask != null) {
+            mUserInfoTask.cancel(false);
+            mUserInfoTask = null;
+        }
+        queryForUserInformation();
+    }
+
+    private void queryForUserInformation() {
+        Context currentUserContext;
+        UserInfo userInfo;
+        try {
+            userInfo = ActivityManager.getService().getCurrentUser();
+            currentUserContext = mContext.createPackageContextAsUser("android", 0,
+                    new UserHandle(userInfo.id));
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Couldn't create user context", e);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't get user info", e);
+            throw new RuntimeException(e);
+        }
+        final int userId = userInfo.id;
+        final boolean isGuest = userInfo.isGuest();
+        final String userName = userInfo.name;
+
+        final Resources res = mContext.getResources();
+        final int avatarSize = Math.max(
+                res.getDimensionPixelSize(R.dimen.multi_user_avatar_expanded_size),
+                res.getDimensionPixelSize(R.dimen.multi_user_avatar_keyguard_size));
+
+        final Context context = currentUserContext;
+        mUserInfoTask = new AsyncTask<Void, Void, UserInfoQueryResult>() {
+
+            @Override
+            protected UserInfoQueryResult doInBackground(Void... params) {
+                final UserManager um = UserManager.get(mContext);
+
+                // Fall back to the UserManager nickname if we can't read the name from the local
+                // profile below.
+                String name = userName;
+                Drawable avatar = null;
+                Bitmap rawAvatar = um.getUserIcon(userId);
+                if (rawAvatar != null) {
+                    avatar = new UserIconDrawable(avatarSize)
+                            .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
+                } else {
+                    avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
+                            /* light= */ true);
+                }
+
+                // If it's a single-user device, get the profile name, since the nickname is not
+                // usually valid
+                if (um.getUsers().size() <= 1) {
+                    // Try and read the display name from the local profile
+                    final Cursor cursor = context.getContentResolver().query(
+                            ContactsContract.Profile.CONTENT_URI, new String[] {
+                                    ContactsContract.CommonDataKinds.Phone._ID,
+                                    ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
+                            }, null, null, null);
+                    if (cursor != null) {
+                        try {
+                            if (cursor.moveToFirst()) {
+                                name = cursor.getString(cursor.getColumnIndex(
+                                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
+                            }
+                        } finally {
+                            cursor.close();
+                        }
+                    }
+                }
+                String userAccount = um.getUserAccount(userId);
+                return new UserInfoQueryResult(name, avatar, userAccount);
+            }
+
+            @Override
+            protected void onPostExecute(UserInfoQueryResult result) {
+                mUserName = result.getName();
+                mUserDrawable = result.getAvatar();
+                mUserAccount = result.getUserAccount();
+                mUserInfoTask = null;
+                notifyChanged();
+            }
+        };
+        mUserInfoTask.execute();
+    }
+
+    private void notifyChanged() {
+        for (OnUserInfoChangedListener listener : mCallbacks) {
+            listener.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
+        }
+    }
+
+    public void onDensityOrFontScaleChanged() {
+        reloadUserInfo();
+    }
+
+    private static class UserInfoQueryResult {
+        private String mName;
+        private Drawable mAvatar;
+        private String mUserAccount;
+
+        public UserInfoQueryResult(String name, Drawable avatar, String userAccount) {
+            mName = name;
+            mAvatar = avatar;
+            mUserAccount = userAccount;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public Drawable getAvatar() {
+            return mAvatar;
+        }
+
+        public String getUserAccount() {
+            return mUserAccount;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 26f74ea..94fc17a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -22,6 +22,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ActivatableNotificationView;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.util.ArrayList;
@@ -52,6 +53,8 @@
     private int mBaseZHeight;
     private int mMaxLayoutHeight;
     private ActivatableNotificationView mLastVisibleBackgroundChild;
+    private float mCurrentScrollVelocity;
+    private int mStatusBarState;
 
     public AmbientState(Context context) {
         reload(context);
@@ -241,4 +244,20 @@
     public ActivatableNotificationView getLastVisibleBackgroundChild() {
         return mLastVisibleBackgroundChild;
     }
+
+    public void setCurrentScrollVelocity(float currentScrollVelocity) {
+        mCurrentScrollVelocity = currentScrollVelocity;
+    }
+
+    public float getCurrentScrollVelocity() {
+        return mCurrentScrollVelocity;
+    }
+
+    public boolean isOnKeyguard() {
+        return mStatusBarState == StatusBarState.KEYGUARD;
+    }
+
+    public void setStatusBarState(int statusBarState) {
+        mStatusBarState = statusBarState;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index d3d58f9..38bb40e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -120,7 +120,7 @@
         }
     }
 
-    private void combineFilter(AnimationFilter filter) {
+    public void combineFilter(AnimationFilter filter) {
         animateAlpha |= filter.animateAlpha;
         animateX |= filter.animateX;
         animateY |= filter.animateY;
@@ -134,7 +134,7 @@
         hasDelays |= filter.hasDelays;
     }
 
-    private void reset() {
+    public void reset() {
         animateAlpha = false;
         animateX = false;
         animateY = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index b8f8cb2..22709f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -213,7 +213,7 @@
         mDividers.add(newIndex, divider);
 
         updateGroupOverflow();
-        row.setIconTransformationAmount(0, false /* isLastChild */);
+        row.setContentTransformationAmount(0, false /* isLastChild */);
     }
 
     public void removeNotification(ExpandableNotificationRow row) {
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 10d995c..543550d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -357,6 +357,7 @@
     private Rect mRequestedClipBounds;
     private boolean mInHeadsUpPinnedMode;
     private boolean mHeadsUpAnimatingAway;
+    private int mStatusBarState;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -575,6 +576,9 @@
      */
     private void updateChildren() {
         updateScrollStateForAddedChildren();
+        mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
+                ? 0
+                : mScroller.getCurrVelocity());
         mAmbientState.setScrollY(mOwnScrollY);
         mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
         if (!isCurrentlyAnimating() && !mNeedsAnimation) {
@@ -715,7 +719,6 @@
             requestChildrenUpdate();
         }
         setStackTranslation(translationY);
-        requestChildrenUpdate();
     }
 
     private void setRequestedClipBounds(Rect clipRect) {
@@ -1185,7 +1188,7 @@
     }
 
     private boolean onKeyguard() {
-        return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
+        return mStatusBarState == StatusBarState.KEYGUARD;
     }
 
     private void setSwipingInProgress(boolean isSwiped) {
@@ -2122,7 +2125,7 @@
             top = mTopPadding;
             bottom = top;
         }
-        if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
+        if (mStatusBarState != StatusBarState.KEYGUARD) {
             top = (int) Math.max(mTopPadding + mStackTranslation, top);
         } else {
             // otherwise the animation from the shade to the keyguard will jump as it's maxed
@@ -2356,7 +2359,7 @@
                 }
                 break;
             case MotionEvent.ACTION_UP:
-                if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
+                if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
                     mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
                 }
@@ -3979,6 +3982,11 @@
         updateClipping();
     }
 
+    public void setStatusBarState(int statusBarState) {
+        mStatusBarState = statusBarState;
+        mAmbientState.setStatusBarState(statusBarState);
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 7afc7ba..50b6d70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -124,7 +124,8 @@
 
     private void updateClipping(StackScrollState resultState,
             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
-        float drawStart = ambientState.getTopPadding() + ambientState.getStackTranslation();
+        float drawStart = !ambientState.isOnKeyguard() ? ambientState.getTopPadding()
+                + ambientState.getStackTranslation() : 0;
         float previousNotificationEnd = 0;
         float previousNotificationStart = 0;
         int childCount = algorithmState.visibleChildren.size();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 8a5ddd4..a8e5ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -99,36 +99,6 @@
             // don't do anything with it
             return;
         }
-        boolean becomesInvisible = this.alpha == 0.0f || this.hidden;
-        boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
-        if (animatingAlpha) {
-            updateAlphaAnimation(view);
-        } else if (view.getAlpha() != this.alpha) {
-            // apply layer type
-            boolean becomesFullyVisible = this.alpha == 1.0f;
-            boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
-                    && view.hasOverlappingRendering();
-            int layerType = view.getLayerType();
-            int newLayerType = newLayerTypeIsHardware
-                    ? View.LAYER_TYPE_HARDWARE
-                    : View.LAYER_TYPE_NONE;
-            if (layerType != newLayerType) {
-                view.setLayerType(newLayerType, null);
-            }
-
-            // apply alpha
-            view.setAlpha(this.alpha);
-        }
-
-        // apply visibility
-        int oldVisibility = view.getVisibility();
-        int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
-        if (newVisibility != oldVisibility) {
-            if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
-                // We don't want views to change visibility when they are animating to GONE
-                view.setVisibility(newVisibility);
-            }
-        }
 
         // apply xTranslation
         boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X);
@@ -163,6 +133,53 @@
         if (view.getScaleY() != this.scaleY) {
             view.setScaleY(this.scaleY);
         }
+
+        boolean becomesInvisible = this.alpha == 0.0f || (this.hidden && !isAnimating(view));
+        boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
+        if (animatingAlpha) {
+            updateAlphaAnimation(view);
+        } else if (view.getAlpha() != this.alpha) {
+            // apply layer type
+            boolean becomesFullyVisible = this.alpha == 1.0f;
+            boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
+                    && view.hasOverlappingRendering();
+            int layerType = view.getLayerType();
+            int newLayerType = newLayerTypeIsHardware
+                    ? View.LAYER_TYPE_HARDWARE
+                    : View.LAYER_TYPE_NONE;
+            if (layerType != newLayerType) {
+                view.setLayerType(newLayerType, null);
+            }
+
+            // apply alpha
+            view.setAlpha(this.alpha);
+        }
+
+        // apply visibility
+        int oldVisibility = view.getVisibility();
+        int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
+        if (newVisibility != oldVisibility) {
+            if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
+                // We don't want views to change visibility when they are animating to GONE
+                view.setVisibility(newVisibility);
+            }
+        }
+    }
+
+    protected boolean isAnimating(View view) {
+        if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_X)) {
+            return true;
+        }
+        if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y)) {
+            return true;
+        }
+        if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z)) {
+            return true;
+        }
+        if (isAnimating(view, TAG_ANIMATOR_ALPHA)) {
+            return true;
+        }
+        return false;
     }
 
     private boolean isAnimating(View view, int tag) {
@@ -482,7 +499,7 @@
                 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
                 child.setTag(TAG_START_TRANSLATION_Y, null);
                 child.setTag(TAG_END_TRANSLATION_Y, null);
-                onYTranslationAnimationFinished();
+                onYTranslationAnimationFinished(child);
             }
         });
         startAnimator(animator, listener);
@@ -491,7 +508,10 @@
         child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
     }
 
-    protected void onYTranslationAnimationFinished() {
+    protected void onYTranslationAnimationFinished(View view) {
+        if (hidden) {
+            view.setVisibility(View.INVISIBLE);
+        }
     }
 
     protected void startAnimator(Animator animator, AnimatorListenerAdapter listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index ebc962d..565ac08 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -92,6 +92,10 @@
         mUserTracker.startTracking();
     }
 
+    public void destroy() {
+        mUserTracker.stopTracking();
+    }
+
     private void upgradeTuner(int oldVersion, int newVersion) {
         if (oldVersion < 1) {
             String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST);
diff --git a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
new file mode 100644
index 0000000..f420921
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016 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.systemui.util;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Builder class to create a {@link LayoutInflater} with various properties.
+ *
+ * Call any desired configuration methods on the Builder and then use
+ * {@link Builder#build} to create the LayoutInflater. This is an alternative to directly using
+ * {@link LayoutInflater#setFilter} and {@link LayoutInflater#setFactory}.
+ * @hide for use by framework
+ */
+public class LayoutInflaterBuilder {
+    private static final String TAG = "LayoutInflaterBuilder";
+
+    private Context mFromContext;
+    private Context mTargetContext;
+    private Map<String, String> mReplaceMap;
+    private Set<Class> mDisallowedClasses;
+    private LayoutInflater mBuiltInflater;
+
+    /**
+     * Creates a new Builder which will construct a LayoutInflater.
+     *
+     * @param fromContext This context's LayoutInflater will be cloned by the Builder using
+     * {@link LayoutInflater#cloneInContext}. By default, the new LayoutInflater will point at
+     * this same Context.
+     */
+    public LayoutInflaterBuilder(@NonNull Context fromContext) {
+        mFromContext = fromContext;
+        mTargetContext = fromContext;
+        mReplaceMap = null;
+        mDisallowedClasses = null;
+        mBuiltInflater = null;
+    }
+
+    /**
+     * Instructs the Builder to point the LayoutInflater at a different Context.
+     *
+     * @param targetContext Context to be provided to
+     * {@link LayoutInflater#cloneInContext(Context)}.
+     * @return Builder object post-modification.
+     */
+    public LayoutInflaterBuilder target(@NonNull Context targetContext) {
+        assertIfAlreadyBuilt();
+        mTargetContext = targetContext;
+        return this;
+    }
+
+    /**
+     * Instructs the Builder to configure the LayoutInflater such that all instances
+     * of one {@link View} will be replaced with instances of another during inflation.
+     *
+     * @param from Instances of this class will be replaced during inflation.
+     * @param to Instances of this class will be inflated as replacements.
+     * @return Builder object post-modification.
+     */
+    public LayoutInflaterBuilder replace(@NonNull Class from, @NonNull Class to) {
+        assertIfAlreadyBuilt();
+        if (mReplaceMap == null) {
+            mReplaceMap = new ArrayMap<String, String>();
+        }
+        mReplaceMap.put(from.getName(), to.getName());
+        return this;
+    }
+
+    /**
+     * Instructs the Builder to configure the LayoutInflater such that any attempt to inflate
+     * a {@link View} of a given type will throw a {@link InflateException}.
+     *
+     * @param disallowedClass The Class type that will be disallowed.
+     * @return Builder object post-modification.
+     */
+    public LayoutInflaterBuilder disallow(@NonNull Class disallowedClass) {
+        assertIfAlreadyBuilt();
+        if (mDisallowedClasses == null) {
+            mDisallowedClasses = new ArraySet<Class>();
+        }
+        mDisallowedClasses.add(disallowedClass);
+        return this;
+    }
+
+    /**
+     * Builds and returns the LayoutInflater.  Afterwards, this Builder can no longer can be
+     * used, all future calls on the Builder will throw {@link AssertionError}.
+     */
+    public LayoutInflater build() {
+        assertIfAlreadyBuilt();
+        mBuiltInflater =
+                LayoutInflater.from(mFromContext).cloneInContext(mTargetContext);
+        setFactoryIfNeeded(mBuiltInflater);
+        setFilterIfNeeded(mBuiltInflater);
+        return mBuiltInflater;
+    }
+
+    private void assertIfAlreadyBuilt() {
+        if (mBuiltInflater != null) {
+            throw new AssertionError("Cannot use this Builder after build() has been called.");
+        }
+    }
+
+    private void setFactoryIfNeeded(LayoutInflater inflater) {
+        if (mReplaceMap == null) {
+            return;
+        }
+        inflater.setFactory(
+                new LayoutInflater.Factory() {
+                    @Override
+                    public View onCreateView(String name, Context context, AttributeSet attrs) {
+                        String replacingClassName = mReplaceMap.get(name);
+                        if (replacingClassName != null) {
+                            try {
+                                return inflater.createView(replacingClassName, null, attrs);
+                            } catch (ClassNotFoundException e) {
+                                Log.e(TAG, "Could not replace " + name
+                                        + " with " + replacingClassName
+                                        + ", Exception: ", e);
+                            }
+                        }
+                        return null;
+                    }
+                });
+    }
+
+    private void setFilterIfNeeded(LayoutInflater inflater) {
+        if (mDisallowedClasses == null) {
+            return;
+        }
+        inflater.setFilter(
+                new LayoutInflater.Filter() {
+                    @Override
+                    public boolean onLoadClass(Class clazz) {
+                        return !mDisallowedClasses.contains(clazz);
+                    }
+                });
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
index f87336c..447edac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
@@ -26,6 +26,8 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -139,7 +141,7 @@
 
     private class HostCallbacks extends FragmentHostCallback<FragmentTestCase> {
         public HostCallbacks() {
-            super(getTrackedContext(), FragmentTestCase.this.mHandler, 0);
+            super(mContext, FragmentTestCase.this.mHandler, 0);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java
deleted file mode 100644
index d64669d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2016 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.systemui;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Base class for tests to check if receivers are left registered, services bound, or other
- * listeners listening.
- */
-public class LeakCheckedTest extends SysuiTestCase {
-    private static final String TAG = "LeakCheckedTest";
-
-    private final Map<String, Tracker> mTrackers = new HashMap<>();
-    private final Map<Class, Object> mLeakCheckers = new ArrayMap<>();
-    private TrackingContext mTrackedContext;
-
-    @Rule
-    public TestWatcher successWatcher = new TestWatcher() {
-        @Override
-        protected void succeeded(Description description) {
-            verify();
-        }
-    };
-
-    @Before
-    public void setup() {
-        mTrackedContext = new TrackingContext(mContext);
-        addSupportedLeakCheckers();
-    }
-
-    public <T> T getLeakChecker(Class<T> cls) {
-        T obj = (T) mLeakCheckers.get(cls);
-        if (obj == null) {
-            Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet");
-        }
-        return obj;
-    }
-
-    public Context getTrackedContext() {
-        return mTrackedContext;
-    }
-
-    private Tracker getTracker(String tag) {
-        Tracker t = mTrackers.get(tag);
-        if (t == null) {
-            t = new Tracker();
-            mTrackers.put(tag, t);
-        }
-        return t;
-    }
-
-    public void verify() {
-        mTrackers.values().forEach(Tracker::verify);
-    }
-
-    public static class Tracker {
-        private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
-
-        LeakInfo getLeakInfo(Object object) {
-            LeakInfo leakInfo = mObjects.get(object);
-            if (leakInfo == null) {
-                leakInfo = new LeakInfo();
-                mObjects.put(object, leakInfo);
-            }
-            return leakInfo;
-        }
-
-        private void verify() {
-            mObjects.values().forEach(LeakInfo::verify);
-        }
-    }
-
-    public static class LeakInfo {
-        private List<Throwable> mThrowables = new ArrayList<>();
-
-        private LeakInfo() {
-        }
-
-        private void addAllocation(Throwable t) {
-            // TODO: Drop off the first element in the stack trace here to have a cleaner stack.
-            mThrowables.add(t);
-        }
-
-        private void clearAllocations() {
-            mThrowables.clear();
-        }
-
-        public void verify() {
-            if (mThrowables.size() == 0) return;
-            Log.e(TAG, "Listener or binding not properly released");
-            for (Throwable t : mThrowables) {
-                Log.e(TAG, "Allocation found", t);
-            }
-            StringWriter writer = new StringWriter();
-            mThrowables.get(0).printStackTrace(new PrintWriter(writer));
-            Assert.fail("Listener or binding not properly released\n"
-                    + writer.toString());
-        }
-    }
-
-    private void addSupportedLeakCheckers() {
-        addListening("bluetooth", BluetoothController.class);
-        addListening("location", LocationController.class);
-        addListening("rotation", RotationLockController.class);
-        addListening("zen", ZenModeController.class);
-        addListening("cast", CastController.class);
-        addListening("hotspot", HotspotController.class);
-        addListening("flashlight", FlashlightController.class);
-        addListening("user", UserInfoController.class);
-        addListening("keyguard", KeyguardMonitor.class);
-        addListening("battery", BatteryController.class);
-        addListening("security", SecurityController.class);
-        addListening("profile", ManagedProfileController.class);
-        addListening("alarm", NextAlarmController.class);
-        NetworkController network = addListening("network", NetworkController.class);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                getTracker("emergency").getLeakInfo(invocation.getArguments()[0])
-                        .addAllocation(new Throwable());
-                return null;
-            }
-        }).when(network).addEmergencyListener(any());
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                getTracker("emergency").getLeakInfo(invocation.getArguments()[0]).clearAllocations();
-                return null;
-            }
-        }).when(network).removeEmergencyListener(any());
-        DataSaverController datasaver = addListening("datasaver", DataSaverController.class);
-        when(network.getDataSaverController()).thenReturn(datasaver);
-    }
-
-    private <T extends CallbackController> T addListening(final String tag, Class<T> cls) {
-        T mock = mock(cls);
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                getTracker(tag).getLeakInfo(invocation.getArguments()[0])
-                        .addAllocation(new Throwable());
-                return null;
-            }
-        }).when(mock).addCallback(any());
-        doAnswer(new Answer<Void>() {
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations();
-                return null;
-            }
-        }).when(mock).removeCallback(any());
-        mLeakCheckers.put(cls, mock);
-        return mock;
-    }
-
-    class TrackingContext extends ContextWrapper {
-        public TrackingContext(Context base) {
-            super(base);
-        }
-
-        @Override
-        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-            getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
-            return super.registerReceiver(receiver, filter);
-        }
-
-        @Override
-        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
-                String broadcastPermission, Handler scheduler) {
-            getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
-            return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
-        }
-
-        @Override
-        public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
-                IntentFilter filter, String broadcastPermission, Handler scheduler) {
-            getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
-            return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
-                    scheduler);
-        }
-
-        @Override
-        public void unregisterReceiver(BroadcastReceiver receiver) {
-            getTracker("receiver").getLeakInfo(receiver).clearAllocations();
-            super.unregisterReceiver(receiver);
-        }
-
-        @Override
-        public boolean bindService(Intent service, ServiceConnection conn, int flags) {
-            getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
-            return super.bindService(service, conn, flags);
-        }
-
-        @Override
-        public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
-                Handler handler, UserHandle user) {
-            getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
-            return super.bindServiceAsUser(service, conn, flags, handler, user);
-        }
-
-        @Override
-        public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
-                UserHandle user) {
-            getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
-            return super.bindServiceAsUser(service, conn, flags, user);
-        }
-
-        @Override
-        public void unbindService(ServiceConnection conn) {
-            getTracker("service").getLeakInfo(conn).clearAllocations();
-            super.unbindService(conn);
-        }
-
-        @Override
-        public void registerComponentCallbacks(ComponentCallbacks callback) {
-            getTracker("component").getLeakInfo(callback).addAllocation(new Throwable());
-            super.registerComponentCallbacks(callback);
-        }
-
-        @Override
-        public void unregisterComponentCallbacks(ComponentCallbacks callback) {
-            getTracker("component").getLeakInfo(callback).clearAllocations();
-            super.unregisterComponentCallbacks(callback);
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 5dac8e5..008580a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -22,6 +22,7 @@
 import android.os.MessageQueue;
 
 import com.android.systemui.utils.TestableContext;
+import com.android.systemui.utils.leaks.Tracker;
 
 import org.junit.After;
 import org.junit.Before;
@@ -29,14 +30,14 @@
 /**
  * Base class that does System UI specific setup.
  */
-public class SysuiTestCase {
+public abstract class SysuiTestCase {
 
     private Handler mHandler;
     protected TestableContext mContext;
 
     @Before
     public void SysuiSetup() throws Exception {
-        mContext = new TestableContext(InstrumentationRegistry.getTargetContext());
+        mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this);
     }
 
     @After
@@ -71,6 +72,11 @@
         }
     }
 
+    // Used for leak tracking, returns null to indicate no leak tracking by default.
+    public Tracker getTracker(String tag) {
+        return null;
+    }
+
     public static final class EmptyRunnable implements Runnable {
         public void run() {
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 6ceaead..1973b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.tuner.TunerService;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -59,7 +60,7 @@
         KeyguardMonitor keyguardMonitor = getLeakChecker(KeyguardMonitor.class);
         when(userSwitcher.getKeyguardMonitor()).thenReturn(keyguardMonitor);
         when(userSwitcher.getUsers()).thenReturn(new ArrayList<>());
-        QSTileHost host = new QSTileHost(getTrackedContext(),
+        QSTileHost host = new QSTileHost(mContext,
                 mock(PhoneStatusBar.class),
                 getLeakChecker(BluetoothController.class),
                 getLeakChecker(LocationController.class),
@@ -86,5 +87,7 @@
         waitForIdleSync(h);
 
         host.destroy();
+        // Ensure the tuner cleans up its persistent listeners.
+        TunerService.get(mContext).destroy();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
index 5179823..bf73416 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
@@ -14,18 +14,31 @@
 
 package com.android.systemui.utils;
 
+import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks;
 import android.content.ContentProviderClient;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.UserHandle;
 import android.provider.Settings;
 
+import com.android.systemui.utils.leaks.Tracker;
+import com.android.systemui.SysuiTestCase;
+
 public class TestableContext extends ContextWrapper {
 
     private final FakeContentResolver mFakeContentResolver;
     private final FakeSettingsProvider mSettingsProvider;
 
-    public TestableContext(Context base) {
+    private Tracker mReceiver;
+    private Tracker mService;
+    private Tracker mComponent;
+
+    public TestableContext(Context base, SysuiTestCase test) {
         super(base);
         mFakeContentResolver = new FakeContentResolver(base);
         ContentProviderClient settings = base.getContentResolver()
@@ -33,6 +46,9 @@
         mSettingsProvider = FakeSettingsProvider.getFakeSettingsProvider(settings,
                 mFakeContentResolver);
         mFakeContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
+        mReceiver = test.getTracker("receiver");
+        mService = test.getTracker("service");
+        mComponent = test.getTracker("component");
     }
 
     public FakeSettingsProvider getSettingsProvider() {
@@ -49,4 +65,69 @@
         // Return this so its always a TestableContext.
         return this;
     }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+        if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
+        return super.registerReceiver(receiver, filter);
+    }
+
+    @Override
+    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+            String broadcastPermission, Handler scheduler) {
+        if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
+        return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
+    }
+
+    @Override
+    public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+            IntentFilter filter, String broadcastPermission, Handler scheduler) {
+        if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
+        return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+                scheduler);
+    }
+
+    @Override
+    public void unregisterReceiver(BroadcastReceiver receiver) {
+        if (mReceiver != null) mReceiver.getLeakInfo(receiver).clearAllocations();
+        super.unregisterReceiver(receiver);
+    }
+
+    @Override
+    public boolean bindService(Intent service, ServiceConnection conn, int flags) {
+        if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+        return super.bindService(service, conn, flags);
+    }
+
+    @Override
+    public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+            Handler handler, UserHandle user) {
+        if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+        return super.bindServiceAsUser(service, conn, flags, handler, user);
+    }
+
+    @Override
+    public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+            UserHandle user) {
+        if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+        return super.bindServiceAsUser(service, conn, flags, user);
+    }
+
+    @Override
+    public void unbindService(ServiceConnection conn) {
+        if (mService != null) mService.getLeakInfo(conn).clearAllocations();
+        super.unbindService(conn);
+    }
+
+    @Override
+    public void registerComponentCallbacks(ComponentCallbacks callback) {
+        if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
+        super.registerComponentCallbacks(callback);
+    }
+
+    @Override
+    public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+        if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations();
+        super.unregisterComponentCallbacks(callback);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
new file mode 100644
index 0000000..0238bf7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.CallbackController;
+
+public class BaseLeakChecker<T> implements CallbackController<T> {
+
+    private final Tracker mTracker;
+
+    public BaseLeakChecker(LeakCheckedTest test, String tag) {
+        mTracker = test.getTracker(tag);
+    }
+
+    protected final Tracker getTracker() {
+        return mTracker;
+    }
+
+    @Override
+    public void addCallback(T listener) {
+        mTracker.getLeakInfo(listener).addAllocation(new Throwable());
+    }
+
+    @Override
+    public void removeCallback(T listener) {
+        mTracker.getLeakInfo(listener).clearAllocations();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
new file mode 100644
index 0000000..fa07d33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import android.os.Bundle;
+
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCallback>
+        implements BatteryController {
+    public FakeBatteryController(LeakCheckedTest test) {
+        super(test, "battery");
+    }
+
+    @Override
+    public void dispatchDemoCommand(String command, Bundle args) {
+
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+
+    }
+
+    @Override
+    public void setPowerSaveMode(boolean powerSave) {
+
+    }
+
+    @Override
+    public boolean isPowerSave() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
new file mode 100644
index 0000000..6074a01
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.BluetoothController.Callback;
+
+import java.util.Collection;
+
+public class FakeBluetoothController extends BaseLeakChecker<Callback> implements
+        BluetoothController {
+
+    public FakeBluetoothController(LeakCheckedTest test) {
+        super(test, "bluetooth");
+    }
+
+    @Override
+    public boolean isBluetoothSupported() {
+        return false;
+    }
+
+    @Override
+    public boolean isBluetoothEnabled() {
+        return false;
+    }
+
+    @Override
+    public int getBluetoothState() {
+        return 0;
+    }
+
+    @Override
+    public boolean isBluetoothConnected() {
+        return false;
+    }
+
+    @Override
+    public boolean isBluetoothConnecting() {
+        return false;
+    }
+
+    @Override
+    public String getLastDeviceName() {
+        return null;
+    }
+
+    @Override
+    public void setBluetoothEnabled(boolean enabled) {
+
+    }
+
+    @Override
+    public Collection<CachedBluetoothDevice> getDevices() {
+        return null;
+    }
+
+    @Override
+    public void connect(CachedBluetoothDevice device) {
+
+    }
+
+    @Override
+    public void disconnect(CachedBluetoothDevice device) {
+
+    }
+
+    @Override
+    public boolean canConfigBluetooth() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
new file mode 100644
index 0000000..08211f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.CastController.Callback;
+
+import java.util.Set;
+
+public class FakeCastController extends BaseLeakChecker<Callback> implements CastController {
+    public FakeCastController(LeakCheckedTest test) {
+        super(test, "cast");
+    }
+
+    @Override
+    public void setDiscovering(boolean request) {
+
+    }
+
+    @Override
+    public void setCurrentUserId(int currentUserId) {
+
+    }
+
+    @Override
+    public Set<CastDevice> getCastDevices() {
+        return null;
+    }
+
+    @Override
+    public void startCasting(CastDevice device) {
+
+    }
+
+    @Override
+    public void stopCasting(CastDevice device) {
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
new file mode 100644
index 0000000..857a785
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DataSaverController.Listener;
+
+public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController {
+
+    public FakeDataSaverController(LeakCheckedTest test) {
+        super(test, "datasaver");
+    }
+
+    @Override
+    public boolean isDataSaverEnabled() {
+        return false;
+    }
+
+    @Override
+    public void setDataSaverEnabled(boolean enabled) {
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
new file mode 100644
index 0000000..630abd7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+
+public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener>
+        implements FlashlightController {
+    public FakeFlashlightController(LeakCheckedTest test) {
+        super(test, "flashlight");
+    }
+
+    @Override
+    public boolean hasFlashlight() {
+        return false;
+    }
+
+    @Override
+    public void setFlashlight(boolean newState) {
+
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return false;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
new file mode 100644
index 0000000..781960d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.HotspotController.Callback;
+
+public class FakeHotspotController extends BaseLeakChecker<Callback> implements HotspotController {
+
+    public FakeHotspotController(LeakCheckedTest test) {
+        super(test, "hotspot");
+    }
+
+    @Override
+    public boolean isHotspotEnabled() {
+        return false;
+    }
+
+    @Override
+    public void setHotspotEnabled(boolean enabled) {
+
+    }
+
+    @Override
+    public boolean isHotspotSupported() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
new file mode 100644
index 0000000..39bbf2d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+public class FakeKeyguardMonitor implements KeyguardMonitor {
+
+    private final BaseLeakChecker<Callback> mCallbackController;
+
+    public FakeKeyguardMonitor(LeakCheckedTest test) {
+        mCallbackController = new BaseLeakChecker<Callback>(test, "keyguard");
+    }
+
+    @Override
+    public void addCallback(Callback callback) {
+        mCallbackController.addCallback(callback);
+    }
+
+    @Override
+    public void removeCallback(Callback callback) {
+        mCallbackController.removeCallback(callback);
+    }
+
+    @Override
+    public boolean isSecure() {
+        return false;
+    }
+
+    @Override
+    public boolean isShowing() {
+        return false;
+    }
+
+    @Override
+    public boolean canSkipBouncer() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
new file mode 100644
index 0000000..eab436c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.LocationController.LocationSettingsChangeCallback;
+
+public class FakeLocationController extends BaseLeakChecker<LocationSettingsChangeCallback>
+        implements LocationController {
+    public FakeLocationController(LeakCheckedTest test) {
+        super(test, "location");
+    }
+
+    @Override
+    public boolean isLocationEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean setLocationEnabled(boolean enabled) {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
new file mode 100644
index 0000000..0ec0d77
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.phone.ManagedProfileController;
+import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
+
+public class FakeManagedProfileController extends BaseLeakChecker<Callback> implements
+        ManagedProfileController {
+    public FakeManagedProfileController(LeakCheckedTest test) {
+        super(test, "profile");
+    }
+
+    @Override
+    public void setWorkModeEnabled(boolean enabled) {
+
+    }
+
+    @Override
+    public boolean hasActiveProfile() {
+        return false;
+    }
+
+    @Override
+    public boolean isWorkModeEnabled() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
new file mode 100644
index 0000000..fcfe9aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+
+public class FakeNetworkController extends BaseLeakChecker<SignalCallback>
+        implements NetworkController {
+
+    private final FakeDataSaverController mDataSaverController;
+    private final BaseLeakChecker<EmergencyListener> mEmergencyChecker;
+
+    public FakeNetworkController(LeakCheckedTest test) {
+        super(test, "network");
+        mDataSaverController = new FakeDataSaverController(test);
+        mEmergencyChecker = new BaseLeakChecker<EmergencyListener>(test, "emergency");
+    }
+
+    @Override
+    public void addEmergencyListener(EmergencyListener listener) {
+        mEmergencyChecker.addCallback(listener);
+    }
+
+    @Override
+    public void removeEmergencyListener(EmergencyListener listener) {
+        mEmergencyChecker.removeCallback(listener);
+    }
+
+    @Override
+    public DataSaverController getDataSaverController() {
+        return mDataSaverController;
+    }
+
+    @Override
+    public boolean hasMobileDataFeature() {
+        return false;
+    }
+
+    @Override
+    public void setWifiEnabled(boolean enabled) {
+
+    }
+
+    @Override
+    public void onUserSwitched(int newUserId) {
+
+    }
+
+    @Override
+    public AccessPointController getAccessPointController() {
+        return null;
+    }
+
+    @Override
+    public DataUsageController getMobileDataController() {
+        return null;
+    }
+
+    @Override
+    public boolean hasVoiceCallingFeature() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
new file mode 100644
index 0000000..707fc4b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+
+public class FakeNextAlarmController extends BaseLeakChecker<NextAlarmChangeCallback>
+        implements NextAlarmController {
+
+    public FakeNextAlarmController(LeakCheckedTest test) {
+        super(test, "alarm");
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
new file mode 100644
index 0000000..00e2404
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
+
+public class FakeRotationLockController extends BaseLeakChecker<RotationLockControllerCallback>
+        implements RotationLockController {
+    public FakeRotationLockController(LeakCheckedTest test) {
+        super(test, "rotation");
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+
+    }
+
+    @Override
+    public int getRotationLockOrientation() {
+        return 0;
+    }
+
+    @Override
+    public boolean isRotationLockAffordanceVisible() {
+        return false;
+    }
+
+    @Override
+    public boolean isRotationLocked() {
+        return false;
+    }
+
+    @Override
+    public void setRotationLocked(boolean locked) {
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
new file mode 100644
index 0000000..331df58
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
+
+public class FakeSecurityController extends BaseLeakChecker<SecurityControllerCallback>
+        implements SecurityController {
+    public FakeSecurityController(LeakCheckedTest test) {
+        super(test, "security");
+    }
+
+    @Override
+    public boolean isDeviceManaged() {
+        return false;
+    }
+
+    @Override
+    public boolean hasProfileOwner() {
+        return false;
+    }
+
+    @Override
+    public String getDeviceOwnerName() {
+        return null;
+    }
+
+    @Override
+    public String getProfileOwnerName() {
+        return null;
+    }
+
+    @Override
+    public CharSequence getDeviceOwnerOrganizationName() {
+        return null;
+    }
+
+    @Override
+    public boolean isVpnEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isVpnRestricted() {
+        return false;
+    }
+
+    @Override
+    public boolean isVpnBranded() {
+        return false;
+    }
+
+    @Override
+    public String getPrimaryVpnName() {
+        return null;
+    }
+
+    @Override
+    public String getProfileVpnName() {
+        return null;
+    }
+
+    @Override
+    public void onUserSwitched(int newUserId) {
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
new file mode 100644
index 0000000..578b310
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
+
+public class FakeUserInfoController extends BaseLeakChecker<OnUserInfoChangedListener>
+        implements UserInfoController {
+    public FakeUserInfoController(LeakCheckedTest test) {
+        super(test, "user_info");
+    }
+
+    @Override
+    public void reloadUserInfo() {
+
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
new file mode 100644
index 0000000..13ea385
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.ZenModeController.Callback;
+
+public class FakeZenModeController extends BaseLeakChecker<Callback> implements ZenModeController {
+    public FakeZenModeController(LeakCheckedTest test) {
+        super(test, "zen");
+    }
+
+    @Override
+    public void setZen(int zen, Uri conditionId, String reason) {
+
+    }
+
+    @Override
+    public int getZen() {
+        return 0;
+    }
+
+    @Override
+    public ZenRule getManualRule() {
+        return null;
+    }
+
+    @Override
+    public ZenModeConfig getConfig() {
+        return null;
+    }
+
+    @Override
+    public long getNextAlarm() {
+        return 0;
+    }
+
+    @Override
+    public void setUserId(int userId) {
+
+    }
+
+    @Override
+    public boolean isZenAvailable() {
+        return false;
+    }
+
+    @Override
+    public ComponentName getEffectsSuppressor() {
+        return null;
+    }
+
+    @Override
+    public boolean isCountdownConditionSupported() {
+        return false;
+    }
+
+    @Override
+    public int getCurrentUser() {
+        return 0;
+    }
+
+    @Override
+    public boolean isVolumeRestricted() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
new file mode 100644
index 0000000..728ed60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+
+import android.util.ArrayMap;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.ManagedProfileController;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Base class for tests to check if receivers are left registered, services bound, or other
+ * listeners listening.
+ */
+public abstract class LeakCheckedTest extends SysuiTestCase {
+    private static final String TAG = "LeakCheckedTest";
+
+    private final Map<String, Tracker> mTrackers = new HashMap<>();
+    private final Map<Class, Object> mLeakCheckers = new ArrayMap<>();
+
+    @Rule
+    public TestWatcher successWatcher = new TestWatcher() {
+        @Override
+        protected void succeeded(Description description) {
+            verify();
+        }
+    };
+
+    public <T> T getLeakChecker(Class<T> cls) {
+        Object obj = mLeakCheckers.get(cls);
+        if (obj == null) {
+            // Lazy create checkers so we only have the ones we need.
+            if (cls == BluetoothController.class) {
+                obj = new FakeBluetoothController(this);
+            } else if (cls == LocationController.class) {
+                obj = new FakeLocationController(this);
+            } else if (cls == RotationLockController.class) {
+                obj = new FakeRotationLockController(this);
+            } else if (cls == ZenModeController.class) {
+                obj = new FakeZenModeController(this);
+            } else if (cls == CastController.class) {
+                obj = new FakeCastController(this);
+            } else if (cls == HotspotController.class) {
+                obj = new FakeHotspotController(this);
+            } else if (cls == FlashlightController.class) {
+                obj = new FakeFlashlightController(this);
+            } else if (cls == UserInfoController.class) {
+                obj = new FakeUserInfoController(this);
+            } else if (cls == KeyguardMonitor.class) {
+                obj = new FakeKeyguardMonitor(this);
+            } else if (cls == BatteryController.class) {
+                obj = new FakeBatteryController(this);
+            } else if (cls == SecurityController.class) {
+                obj = new FakeSecurityController(this);
+            } else if (cls == ManagedProfileController.class) {
+                obj = new FakeManagedProfileController(this);
+            } else if (cls == NextAlarmController.class) {
+                obj = new FakeNextAlarmController(this);
+            } else if (cls == NetworkController.class) {
+                obj = new FakeNetworkController(this);
+            } else {
+                Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet");
+            }
+            mLeakCheckers.put(cls, obj);
+        }
+        return (T) obj;
+    }
+
+    @Override
+    public Tracker getTracker(String tag) {
+        Tracker t = mTrackers.get(tag);
+        if (t == null) {
+            t = new Tracker();
+            mTrackers.put(tag, t);
+        }
+        return t;
+    }
+
+    public void verify() {
+        mTrackers.values().forEach(Tracker::verify);
+    }
+
+    public <T extends CallbackController> T addListening(T mock, Class<T> cls, String tag) {
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                getTracker(tag).getLeakInfo(invocation.getArguments()[0])
+                        .addAllocation(new Throwable());
+                return null;
+            }
+        }).when(mock).addCallback(any());
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations();
+                return null;
+            }
+        }).when(mock).removeCallback(any());
+        mLeakCheckers.put(cls, mock);
+        return mock;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java
new file mode 100644
index 0000000..1d016fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import android.util.Log;
+
+import org.junit.Assert;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LeakInfo {
+    private static final String TAG = "LeakInfo";
+    private List<Throwable> mThrowables = new ArrayList<>();
+
+    LeakInfo() {
+    }
+
+    public void addAllocation(Throwable t) {
+        // TODO: Drop off the first element in the stack trace here to have a cleaner stack.
+        mThrowables.add(t);
+    }
+
+    public void clearAllocations() {
+        mThrowables.clear();
+    }
+
+    void verify() {
+        if (mThrowables.size() == 0) return;
+        Log.e(TAG, "Listener or binding not properly released");
+        for (Throwable t : mThrowables) {
+            Log.e(TAG, "Allocation found", t);
+        }
+        StringWriter writer = new StringWriter();
+        mThrowables.get(0).printStackTrace(new PrintWriter(writer));
+        Assert.fail("Listener or binding not properly released\n"
+                + writer.toString());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java
new file mode 100644
index 0000000..26ffd10
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 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.systemui.utils.leaks;
+
+import android.util.ArrayMap;
+
+import com.android.systemui.utils.leaks.LeakInfo;
+
+import java.util.Map;
+
+public class Tracker {
+    private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
+
+    public LeakInfo getLeakInfo(Object object) {
+        LeakInfo leakInfo = mObjects.get(object);
+        if (leakInfo == null) {
+            leakInfo = new LeakInfo();
+            mObjects.put(object, leakInfo);
+        }
+        return leakInfo;
+    }
+
+    void verify() {
+        mObjects.values().forEach(LeakInfo::verify);
+    }
+}
diff --git a/proto/src/ipconnectivity.proto b/proto/src/ipconnectivity.proto
index 29b318f..cf372bc 100644
--- a/proto/src/ipconnectivity.proto
+++ b/proto/src/ipconnectivity.proto
@@ -17,6 +17,22 @@
   optional int32 network_id = 1;
 };
 
+// Transport describes a physical technology used by a network. It is a subset
+// of the TRANSPORT_* constants defined in android.net.NetworkCapabilities.
+enum Transport {
+  UNKNOWN   = 0;
+  BLUETOOTH = 1;
+  CELLULAR  = 2;
+  ETHERNET  = 3;
+  WIFI      = 4;
+};
+
+// A pair of (key, value) integers for describing histogram-like statistics.
+message Pair {
+  optional int32 key = 1;
+  optional int32 value = 2;
+};
+
 // Logs changes in the system default network. Changes can be 1) acquiring a
 // default network with no previous default, 2) a switch of the system default
 // network to a new default network, 3) a loss of the system default network.
@@ -49,7 +65,8 @@
 // This message is associated to android.net.metrics.IpReachabilityEvent.
 message IpReachabilityEvent {
   // The interface name (wlan, rmnet, lo, ...) on which the probe was sent.
-  optional string if_name = 1;
+  // Deprecated since version 2, replaced by transport field.
+  optional string if_name = 1 [deprecated = true];
 
   // The event type code of the probe, represented by constants defined in
   // android.net.metrics.IpReachabilityEvent.
@@ -93,6 +110,7 @@
 
 // Logs DNS lookup latencies. Repeated fields must have the same length.
 // This message is associated to android.net.metrics.DnsEvent.
+// Deprecated since version 2.
 message DNSLookupBatch {
   // The id of the network on which the DNS lookups took place.
   optional NetworkId network_id = 1;
@@ -107,13 +125,62 @@
   repeated int32 latencies_ms = 4;
 };
 
+// Represents a collections of DNS lookup latencies and counters for a
+// particular combination of DNS query type and return code.
+// Since version 2.
+message DNSLatencies {
+  // The type of the DNS lookups, as defined in android.net.metrics.DnsEvent.
+  // Acts as a key for a set of DNS query results.
+  // Possible values are: 0 for getaddrinfo, 1 for gethostbyname.
+  optional int32 type = 1;
+
+  // The return value of the DNS resolver for the DNS lookups.
+  // Acts as a key for a set of DNS query results.
+  // Possible values are: 0 for success, or errno code for failures.
+  optional int32 return_code = 2;
+
+  // The number of query operations recorded.
+  optional int32 query_count = 3;
+
+  // The number of query operations returning A IPv4 records.
+  optional int32 a_count = 4;
+
+  // The number of query operations returning AAAA IPv6 records.
+  optional int32 aaaa_count = 5;
+
+  // The time it took for each DNS lookup to complete. The number of repeated
+  // values can be less than query_count in case of event rate-limiting.
+  repeated int32 latencies_ms = 6;
+};
+
+// Represents latency and errno statistics of the connect() system call.
+// Since version 2.
+message ConnectStatistics {
+  // The number of connect() operations recorded.
+  optional int32 connect_count = 1;
+
+  // The number of connect() operations with IPv6 socket address.
+  optional int32 ipv6_addr_count = 2;
+
+  // The time it took for each successful connect() operation to complete.
+  // The number of repeated values can be less than connect_count in case of
+  // event rate-limiting.
+  repeated int32 latencies_ms = 3;
+
+  // Counts of all error values returned by failed connect() operations.
+  // The Pair key field is the errno code. The Pair value field is the count
+  // for that errno code.
+  repeated Pair errnos_counters = 4;
+};
+
 // Represents a DHCP event on a single interface, which can be a DHCPClient
 // state transition or a response packet parsing error.
 // This message is associated to android.net.metrics.DhcpClientEvent and
 // android.net.metrics.DhcpErrorEvent.
 message DHCPEvent {
   // The interface name (wlan, rmnet, lo, ...) on which the event happened.
-  optional string if_name = 1;
+  // Deprecated since version 2, replaced by transport field.
+  optional string if_name = 1 [deprecated = true];
 
   oneof value {
     // The name of a state in the DhcpClient state machine, represented by
@@ -217,7 +284,8 @@
 // This message is associated to android.net.metrics.IpManagerEvent.
 message IpProvisioningEvent {
   // The interface name (wlan, rmnet, lo, ...) on which the probe was sent.
-  optional string if_name = 1;
+  // Deprecated since version 2, replaced by transport field.
+  optional string if_name = 1 [deprecated = true];
 
   // The code of the IP provisioning event, represented by constants defined in
   // android.net.metrics.IpManagerEvent.
@@ -228,11 +296,15 @@
 }
 
 // Represents one of the IP connectivity event defined in this file.
-// Next tag: 12
+// Next tag: 15
 message IpConnectivityEvent {
   // Time in ms when the event was recorded.
   optional int64 time_ms = 1;
 
+  // Physical transport of the network on which the event happened.
+  // Since version 2.
+  optional Transport transport = 12;
+
   // Event type.
   oneof event {
 
@@ -246,7 +318,14 @@
     NetworkEvent network_event = 4;
 
     // A batch of DNS lookups.
-    DNSLookupBatch dns_lookup_batch = 5;
+    // Deprecated in the nyc-mr2 release since version 2, and replaced by dns_latencies.
+    DNSLookupBatch dns_lookup_batch = 5 [deprecated = true];
+
+    // DNS lookup latency statistics.
+    DNSLatencies dns_latencies = 13;
+
+    // Connect latency and errno statistics.
+    ConnectStatistics connect_statistics = 14;
 
     // A DHCP client event or DHCP receive error.
     DHCPEvent dhcp_event = 6;
@@ -277,9 +356,9 @@
   optional int32 dropped_events = 2;
 
   // The version number of the metrics events being collected.
-  //  nyc-dev: not populated, implicitly 0
-  //  nyc-dr1: not populated, implicitly 1 (sailfish and marlin only)
-  //  nyc-mr1: not populated, implicitly 1
-  //  nyc-mr2: 2
+  //  nyc-dev: not populated, implicitly 0.
+  //  nyc-dr1: not populated, implicitly 1 (sailfish and marlin only).
+  //  nyc-mr1: not populated, implicitly 1.
+  //  nyc-mr2: 2.
   optional int32 version = 3;
 };
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 4c9ea58..6412e01 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -36,10 +36,12 @@
 import android.net.wifi.WifiConfiguration;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -52,11 +54,11 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Backing service for {@link android.net.NetworkScoreManager}.
@@ -68,7 +70,8 @@
 
     private final Context mContext;
     private final NetworkScorerAppManager mNetworkScorerAppManager;
-    private final Map<Integer, INetworkScoreCache> mScoreCaches;
+    @GuardedBy("mScoreCaches")
+    private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
     /** Lock used to update mPackageMonitor when scorer package changes occur. */
     private final Object mPackageMonitorLock = new Object[0];
 
@@ -166,7 +169,7 @@
     NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager) {
         mContext = context;
         mNetworkScorerAppManager = networkScoreAppManager;
-        mScoreCaches = new HashMap<>();
+        mScoreCaches = new ArrayMap<>();
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
         // TODO: Need to update when we support per-user scorers. http://b/23422763
         mContext.registerReceiverAsUser(
@@ -276,7 +279,7 @@
         }
 
         // Separate networks by type.
-        Map<Integer, List<ScoredNetwork>> networksByType = new HashMap<>();
+        Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
         for (ScoredNetwork network : networks) {
             List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
             if (networkList == null) {
@@ -287,19 +290,32 @@
         }
 
         // Pass the scores of each type down to the appropriate network scorer.
-        for (Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
-            INetworkScoreCache scoreCache = mScoreCaches.get(entry.getKey());
-            if (scoreCache != null) {
-                try {
-                    scoreCache.updateScores(entry.getValue());
-                } catch (RemoteException e) {
-                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                        Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
+        for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
+            final RemoteCallbackList<INetworkScoreCache> callbackList;
+            final boolean isEmpty;
+            synchronized (mScoreCaches) {
+                callbackList = mScoreCaches.get(entry.getKey());
+                isEmpty = callbackList == null || callbackList.getRegisteredCallbackCount() == 0;
+            }
+            if (isEmpty) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
+                }
+                continue;
+            }
+
+            sendCallback(new Consumer<INetworkScoreCache>() {
+                @Override
+                public void accept(INetworkScoreCache networkScoreCache) {
+                    try {
+                        networkScoreCache.updateScores(entry.getValue());
+                    } catch (RemoteException e) {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
+                        }
                     }
                 }
-            } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
-            }
+            }, Collections.singleton(callbackList));
         }
 
         return true;
@@ -394,28 +410,52 @@
 
     /** Clear scores. Callers are responsible for checking permissions as appropriate. */
     private void clearInternal() {
-        Set<INetworkScoreCache> cachesToClear = getScoreCaches();
-
-        for (INetworkScoreCache scoreCache : cachesToClear) {
-            try {
-                scoreCache.clearScores();
-            } catch (RemoteException e) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "Unable to clear scores", e);
+        sendCallback(new Consumer<INetworkScoreCache>() {
+            @Override
+            public void accept(INetworkScoreCache networkScoreCache) {
+                try {
+                    networkScoreCache.clearScores();
+                } catch (RemoteException e) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Unable to clear scores", e);
+                    }
                 }
             }
-        }
+        }, getScoreCacheLists());
     }
 
     @Override
     public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
         mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
         synchronized (mScoreCaches) {
-            if (mScoreCaches.containsKey(networkType)) {
-                throw new IllegalArgumentException(
-                        "Score cache already registered for type " + networkType);
+            RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
+            if (callbackList == null) {
+                callbackList = new RemoteCallbackList<>();
+                mScoreCaches.put(networkType, callbackList);
             }
-            mScoreCaches.put(networkType, scoreCache);
+            if (!callbackList.register(scoreCache)) {
+                if (callbackList.getRegisteredCallbackCount() == 0) {
+                    mScoreCaches.remove(networkType);
+                }
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+        mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
+        synchronized (mScoreCaches) {
+            RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
+            if (callbackList == null || !callbackList.unregister(scoreCache)) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "Unable to unregister NetworkScoreCache for type " + networkType);
+                }
+            } else if (callbackList.getRegisteredCallbackCount() == 0) {
+                mScoreCaches.remove(networkType);
+            }
         }
     }
 
@@ -430,7 +470,7 @@
     }
 
     @Override
-    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+    protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
         mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
         NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
         if (currentScorer == null) {
@@ -439,13 +479,17 @@
         }
         writer.println("Current scorer: " + currentScorer.mPackageName);
 
-        for (INetworkScoreCache scoreCache : getScoreCaches()) {
-            try {
-                TransferPipe.dumpAsync(scoreCache.asBinder(), fd, args);
-            } catch (IOException | RemoteException e) {
-                writer.println("Failed to dump score cache: " + e);
+        sendCallback(new Consumer<INetworkScoreCache>() {
+            @Override
+            public void accept(INetworkScoreCache networkScoreCache) {
+                try {
+                  TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
+                } catch (IOException | RemoteException e) {
+                  writer.println("Failed to dump score cache: " + e);
+                }
             }
-        }
+        }, getScoreCacheLists());
+
         if (mServiceConnection != null) {
             mServiceConnection.dump(fd, writer, args);
         } else {
@@ -455,14 +499,30 @@
     }
 
     /**
-     * Returns a set of all score caches that are currently active.
+     * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
      *
      * <p>May be used to perform an action on all score caches without potentially strange behavior
      * if a new scorer is registered during that action's execution.
      */
-    private Set<INetworkScoreCache> getScoreCaches() {
+    private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
         synchronized (mScoreCaches) {
-            return new HashSet<>(mScoreCaches.values());
+            return new ArrayList<>(mScoreCaches.values());
+        }
+    }
+
+    private void sendCallback(Consumer<INetworkScoreCache> consumer,
+            Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
+        for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
+            synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
+                final int count = callbackList.beginBroadcast();
+                try {
+                    for (int i = 0; i < count; i++) {
+                        consumer.accept(callbackList.getBroadcastItem(i));
+                    }
+                } finally {
+                    callbackList.finishBroadcast();
+                }
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a5b3020..e6e4b2d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -269,10 +269,8 @@
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
@@ -7496,45 +7494,51 @@
 
     @Override
     public void enterPictureInPictureMode(IBinder token) {
-        enterPictureInPictureMode(token, DEFAULT_DISPLAY, null /* aspectRatio */);
+        enterPictureInPictureMode(token, DEFAULT_DISPLAY, -1f /* aspectRatio */,
+                false /* checkAspectRatio */);
     }
 
     @Override
     public void enterPictureInPictureModeWithAspectRatio(IBinder token, float aspectRatio) {
-        enterPictureInPictureMode(token, DEFAULT_DISPLAY, aspectRatio);
+        enterPictureInPictureMode(token, DEFAULT_DISPLAY, aspectRatio, true /* checkAspectRatio */);
     }
 
-    public void enterPictureInPictureMode(IBinder token, int displayId, Float aspectRatio) {
+    private void enterPictureInPictureMode(IBinder token, int displayId, float aspectRatio,
+            boolean checkAspectRatio) {
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized(this) {
-                if (!mSupportsPictureInPicture) {
-                    throw new IllegalStateException("enterPictureInPictureMode: "
-                            + "Device doesn't support picture-in-picture mode.");
-                }
+                final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
+                        "enterPictureInPictureMode", token, aspectRatio, checkAspectRatio,
+                        true /* checkActivityVisibility */);
 
-                final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-                if (r == null) {
-                    throw new IllegalStateException("enterPictureInPictureMode: "
-                            + "Can't find activity for token=" + token);
-                }
+                enterPictureInPictureModeLocked(r, displayId, aspectRatio,
+                        true /* moveHomeStackToFront */, "enterPictureInPictureMode");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
 
-                if (!r.supportsPictureInPicture()) {
-                    throw new IllegalArgumentException("enterPictureInPictureMode: "
-                            + "Picture-In-Picture not supported for r=" + r);
-                }
+    void enterPictureInPictureModeLocked(ActivityRecord r, int displayId, float aspectRatio,
+            boolean moveHomeStackToFront, String reason) {
+        final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
+                ? mWindowManager.getPictureInPictureBounds(displayId, aspectRatio)
+                : mWindowManager.getPictureInPictureDefaultBounds(displayId);
+        mStackSupervisor.moveActivityToPinnedStackLocked(r, reason, bounds, moveHomeStackToFront);
+    }
 
-                if (aspectRatio != null && !isValidPictureInPictureAspectRatio(aspectRatio)) {
-                    throw new IllegalArgumentException(String.format("enterPictureInPictureMode: "
-                            + "Aspect ratio is too extreme (must be between %f and %f).",
-                                    mMinPipAspectRatio, mMaxPipAspectRatio));
-                }
+    @Override
+    public void enterPictureInPictureModeOnMoveToBackground(IBinder token,
+            boolean enterPictureInPictureOnMoveToBg) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized(this) {
+                final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
+                        "requestAutoEnterPictureInPicture", token, -1f /* aspectRatio */,
+                        false /* checkAspectRatio */, false /* checkActivityVisibility */);
 
-                final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
-                        ? mWindowManager.getPictureInPictureBounds(displayId, aspectRatio)
-                        : mWindowManager.getPictureInPictureDefaultBounds(displayId);
-                mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode",
-                        bounds);
+                r.supportsPipOnMoveToBackground = enterPictureInPictureOnMoveToBg;
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -7546,33 +7550,68 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized(this) {
-                final ActivityRecord r = ActivityRecord.forTokenLocked(token);
-                if (r == null || r.getStack().mStackId != PINNED_STACK_ID) {
-                    throw new IllegalStateException("setPictureInPictureAspectRatio: "
-                            + "Requesting activity must be in picture-in-picture mode.");
-                }
+                final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
+                        "setPictureInPictureAspectRatio", token, aspectRatio,
+                        true /* checkAspectRatio */, false /* checkActivityVisibility */);
 
-                if (!isValidPictureInPictureAspectRatio(aspectRatio)) {
-                    throw new IllegalArgumentException(String.format(
-                            "setPictureInPictureAspectRatio: Aspect ratio is too extreme (must be "
-                                    + "between %f and %f).", mMinPipAspectRatio,
-                            mMaxPipAspectRatio));
+                if (r.getStack().getStackId() == PINNED_STACK_ID) {
+                    // If the activity is already in picture-in-picture, update the pinned stack now
+                    mWindowManager.setPictureInPictureAspectRatio(aspectRatio);
                 }
-
-                mWindowManager.setPictureInPictureAspectRatio(aspectRatio);
+                r.pictureInPictureAspectRatio = aspectRatio;
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
-    private boolean isValidPictureInPictureAspectRatio(Float aspectRatio) {
-        if (aspectRatio == null) {
-            return false;
-        }
+    private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
         return mMinPipAspectRatio <= aspectRatio && aspectRatio <= mMaxPipAspectRatio;
     }
 
+    /**
+     * Checks the state of the system and the activity associated with the given {@param token} to
+     * verify that picture-in-picture is supported for that activity.
+     *
+     * @param checkAspectRatio whether or not to check {@param aspectRatio} is within a valid range
+     * @param checkActivityVisibility whether or not to enforce that the activity is currently
+     *                                visible
+     *
+     * @return the activity record for the given {@param token} if all the checks pass.
+     */
+    private ActivityRecord ensureValidPictureInPictureActivityLocked(String caller, IBinder token,
+            float aspectRatio, boolean checkAspectRatio, boolean checkActivityVisibility) {
+        if (!mSupportsPictureInPicture) {
+            throw new IllegalStateException(caller
+                    + ": Device doesn't support picture-in-picture mode.");
+        }
+
+        final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+        if (r == null) {
+            throw new IllegalStateException(caller
+                    + ": Can't find activity for token=" + token);
+        }
+
+        if (!r.canEnterPictureInPicture(checkActivityVisibility)) {
+            throw new IllegalArgumentException(caller
+                    + "Current activity does not support picture-in-picture or is not "
+                    + "visible r=" + r);
+        }
+
+        if (r.getStack().isHomeStack()) {
+            throw new IllegalStateException(caller
+                    + ": Activities on the home stack not supported");
+        }
+
+        if (checkAspectRatio && !isValidPictureInPictureAspectRatio(aspectRatio)) {
+            throw new IllegalArgumentException(String.format(caller
+                    + ": Aspect ratio is too extreme (must be between %f and %f).",
+                            mMinPipAspectRatio, mMaxPipAspectRatio));
+        }
+
+        return r;
+    }
+
     // =========================================================
     // PROCESS INFO
     // =========================================================
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index d2a560f..13c422b 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -21,7 +21,6 @@
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
@@ -84,7 +83,6 @@
 import android.util.TimeUtils;
 import android.view.AppTransitionAnimationSpec;
 import android.view.IApplicationToken;
-import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.app.ResolverActivity;
@@ -218,6 +216,14 @@
     boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
     boolean immersive;      // immersive mode (don't interrupt if possible)
     boolean forceNewConfig; // force re-create with new config next time
+    boolean supportsPipOnMoveToBackground;   // Supports automatically entering picture-in-picture
+            // when this activity is hidden. This flag is requested by the activity.
+    private boolean enterPipOnMoveToBackground; // Flag to enter picture in picture when this
+            // activity is made invisible. This flag is set specifically when another task is being
+            // launched or moved to the front which may cause this activity to try and enter PiP
+            // when it is next made invisible.
+    float pictureInPictureAspectRatio; // The aspect ratio to use when auto-entering
+            // picture-in-picture
     int launchCount;        // count of launches since last state
     long lastLaunchTime;    // time of last launch of this activity
     ComponentName requestedVrComponent; // the requested component for handling VR mode.
@@ -434,6 +440,11 @@
         if (info != null) {
             pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
         }
+        if (supportsPipOnMoveToBackground) {
+            pw.println(prefix + "supportsPipOnMoveToBackground=1 "
+                    + "enterPipOnMoveToBackground="
+                            + (enterPipOnMoveToBackground ? 1 : 0));
+        }
     }
 
     private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
@@ -842,6 +853,23 @@
     }
 
     /**
+     * If this activity has requested that it auto-enter picture-in-picture and we can actually do
+     * this, then mark it to enter picture in picture at that point.
+     */
+    void setEnterPipOnMoveToBackground(boolean enterPipOnInvisible) {
+        if (supportsPipOnMoveToBackground) {
+            enterPipOnMoveToBackground = enterPipOnInvisible;
+        }
+    }
+
+    /**
+     * @return whether to enter PiP when this activity is made invisible.
+     */
+    public boolean shouldEnterPictureInPictureOnInvisible() {
+        return enterPipOnMoveToBackground;
+    }
+
+    /**
      * @return Stack value from current task, null if there is no task.
      */
     ActivityStack getStack() {
@@ -922,10 +950,34 @@
                 && info.resizeMode != RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
     }
 
+    /**
+     * @return whether this activity's resize mode supports PIP.
+     */
     boolean supportsPictureInPicture() {
         return !isHomeActivity() && info.resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
     }
 
+    /**
+     * @return whether this activity is currently allowed to enter PIP, if
+     * {@param checkActivityVisibility} is set, then the current activity visibility is taken into
+     * account.
+     */
+    boolean canEnterPictureInPicture(boolean checkActivityVisibility) {
+        if (!checkActivityVisibility) {
+            return supportsPictureInPicture();
+        }
+
+        if (supportsPictureInPicture() && visible) {
+            switch (state) {
+                case RESUMED:
+                case PAUSING:
+                case PAUSED:
+                    return true;
+            }
+        }
+        return false;
+    }
+
     boolean canGoInDockedStack() {
         return !isHomeActivity() && isResizeableOrForced();
     }
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index d94d3cd..d160a46 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -22,7 +22,6 @@
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
@@ -723,7 +722,6 @@
 
         mStacks.remove(this);
         int addIndex = mStacks.size();
-
         if (addIndex > 0) {
             final ActivityStack topStack = mStacks.get(addIndex - 1);
             if (StackId.isAlwaysOnTop(topStack.mStackId) && topStack != this) {
@@ -1718,7 +1716,9 @@
                                 + stackInvisible + " behindFullscreenActivity="
                                 + behindFullscreenActivity + " mLaunchTaskBehind="
                                 + r.mLaunchTaskBehind);
-                        makeInvisible(r, visibleBehind);
+                        if (!enterPictureInPictureOnActivityInvisible(r)) {
+                            makeInvisible(r, visibleBehind);
+                        }
                     }
                 }
                 if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
@@ -1876,6 +1876,32 @@
         return false;
     }
 
+    /**
+     * Attempts to enter picture-in-picture if the activity that is being made invisible supports
+     * it.  If not, then
+     *
+     * @return whether or not picture-in-picture mode was entered.
+     */
+    private boolean enterPictureInPictureOnActivityInvisible(ActivityRecord r) {
+        final boolean hasPinnedStack =
+                mStackSupervisor.getStack(PINNED_STACK_ID) != null;
+        if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, " enterPictureInPictureOnInvisible="
+                + r.shouldEnterPictureInPictureOnInvisible()
+                + " hasPinnedStack=" + hasPinnedStack);
+        if (!hasPinnedStack && r.visible && r.shouldEnterPictureInPictureOnInvisible()) {
+            r.setEnterPipOnMoveToBackground(false);
+
+            // Enter picture in picture, but don't move the home stack to the front
+            // since it will affect the focused stack's visibility and occlude
+            // starting activities
+            mService.enterPictureInPictureModeLocked(r, r.getDisplayId(),
+                    r.pictureInPictureAspectRatio, false /* moveHomeStackToFront */,
+                    "ensureActivitiesVisibleLocked");
+            return true;
+        }
+        return false;
+    }
+
     private void makeInvisible(ActivityRecord r, ActivityRecord visibleBehind) {
         if (!r.visible) {
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
@@ -2613,8 +2639,8 @@
         mWindowManager.moveTaskToTop(task.taskId);
     }
 
-    final void startActivityLocked(ActivityRecord r, boolean newTask, boolean keepCurTransition,
-            ActivityOptions options) {
+    final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
+            boolean newTask, boolean keepCurTransition, ActivityOptions options) {
         TaskRecord rTask = r.task;
         final int taskId = rTask.taskId;
         // mLaunchTaskBehind tasks get placed at the back of the task stack.
@@ -2693,11 +2719,20 @@
                 mWindowManager.prepareAppTransition(TRANSIT_NONE, keepCurTransition);
                 mNoAnimActivities.add(r);
             } else {
-                mWindowManager.prepareAppTransition(newTask
-                        ? r.mLaunchTaskBehind
-                                ? TRANSIT_TASK_OPEN_BEHIND
-                                : TRANSIT_TASK_OPEN
-                        : TRANSIT_ACTIVITY_OPEN, keepCurTransition);
+                int transit = TRANSIT_ACTIVITY_OPEN;
+                if (newTask) {
+                    if (r.mLaunchTaskBehind) {
+                        transit = TRANSIT_TASK_OPEN_BEHIND;
+                    } else {
+                        // If a new task is being launched, then mark the existing top activity to
+                        // enter picture-in-picture if it supports auto-entering PiP
+                        if (focusedTopActivity != null) {
+                            focusedTopActivity.setEnterPipOnMoveToBackground(true);
+                        }
+                        transit = TRANSIT_TASK_OPEN;
+                    }
+                }
+                mWindowManager.prepareAppTransition(transit, keepCurTransition);
                 mNoAnimActivities.remove(r);
             }
             addConfigOverride(r, task);
@@ -4193,6 +4228,8 @@
             AppTimeTracker timeTracker, String reason) {
         if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
 
+        final ActivityRecord focusedTopActivity = mStackSupervisor.getFocusedStack() != null
+                ? mStackSupervisor.getFocusedStack().topActivity() : null;
         final int numTasks = mTaskHistory.size();
         final int index = mTaskHistory.indexOf(tr);
         if (numTasks == 0 || index < 0)  {
@@ -4238,6 +4275,11 @@
         } else {
             updateTransitLocked(TRANSIT_TASK_TO_FRONT, options);
         }
+        // If a new task is moved to the front, then mark the existing top activity to enter
+        // picture-in-picture if it supports auto-entering PiP
+        if (focusedTopActivity != null) {
+            focusedTopActivity.setEnterPipOnMoveToBackground(true);
+        }
 
         mStackSupervisor.resumeFocusedStackTopActivityLocked();
         EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, tr.taskId);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index dde948f..281812c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2322,6 +2322,11 @@
             return true;
         }
 
+        if (!task.canResizeToBounds(bounds)) {
+            throw new IllegalArgumentException("resizeTaskLocked: Can not resize task=" + task
+                    + " to bounds=" + bounds + " resizeMode=" + task.mResizeMode);
+        }
+
         // Do not move the task to another stack here.
         // This method assumes that the task is already placed in the right stack.
         // we do not mess with that decision and we only do the resize!
@@ -2638,11 +2643,13 @@
             return false;
         }
 
-        moveActivityToPinnedStackLocked(r, "moveTopActivityToPinnedStack", bounds);
+        moveActivityToPinnedStackLocked(r, "moveTopActivityToPinnedStack", bounds,
+                true /* moveHomeStackToFront */);
         return true;
     }
 
-    void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds) {
+    void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds,
+            boolean moveHomeStackToFront) {
         mWindowManager.deferSurfaceLayout();
         try {
             final TaskRecord task = r.task;
@@ -2666,7 +2673,7 @@
             if (task.mActivities.size() == 1) {
                 // There is only one activity in the task. So, we can just move the task over to
                 // the stack without re-parenting the activity in a different task.
-                if (task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE) {
+                if (moveHomeStackToFront && task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE) {
                     // Move the home stack forward if the task we just moved to the pinned stack
                     // was launched from home so home should be visible behind it.
                     moveHomeStackToFront(reason);
@@ -2674,6 +2681,8 @@
                 moveTaskToStackLocked(
                         task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS, reason, !ANIMATE);
             } else {
+                // There are multiple activities in the task and moving the top activity should
+                // reveal/leave the other activities in their original task
                 stack.moveActivityToStack(r);
             }
         } finally {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 64bf3ad..d0960a0 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1068,6 +1068,7 @@
         // If the activity being launched is the same as the one currently at the top, then
         // we need to check if it should only be launched once.
         final ActivityStack topStack = mSupervisor.mFocusedStack;
+        final ActivityRecord topFocused = topStack.topActivity();
         final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
         final boolean dontStart = top != null && mStartActivity.resultTo == null
                 && top.realActivity.equals(mStartActivity.realActivity)
@@ -1139,7 +1140,8 @@
 
         sendPowerHintForLaunchStartIfNeeded(false /* forceSend */);
 
-        mTargetStack.startActivityLocked(mStartActivity, newTask, mKeepCurTransition, mOptions);
+        mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
+                mOptions);
         if (mDoResume) {
             final ActivityRecord topTaskActivity = mStartActivity.task.topRunningActivityLocked();
             if (!mTargetStack.isFocusable()
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 5c352e1..383f106 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -73,6 +73,9 @@
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
@@ -1077,12 +1080,32 @@
                 && !mTemporarilyUnresizable;
     }
 
+    /**
+     * Check that a given bounds matches the application requested orientation.
+     *
+     * @param bounds The bounds to be tested.
+     * @return True if the requested bounds are okay for a resizing request.
+     */
+    boolean canResizeToBounds(Rect bounds) {
+        if (bounds == null || getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
+            // Note: If not on the freeform workspace, we ignore the bounds.
+            return true;
+        }
+        final boolean landscape = bounds.width() > bounds.height();
+        if (mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION) {
+            return mBounds == null || landscape == (mBounds.width() > mBounds.height());
+        }
+        return (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY || !landscape)
+                && (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY || landscape);
+    }
+
     boolean isOnTopLauncher() {
         return isHomeTask() && mIsOnTopLauncher;
     }
 
     boolean canGoInDockedStack() {
-        return isResizeable();
+        return isResizeable() &&
+                !ActivityInfo.isPreserveOrientationMode(mResizeMode);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 01a50b7..0ad4e0a 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -135,7 +135,7 @@
         mService = service;
         mDisplayContent = displayContent;
         mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
-        mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
+        mMotionHelper = new PipMotionHelper(UiThread.getHandler());
         mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
         reloadResources();
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 29afc8d..612af75 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -19,7 +19,9 @@
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -280,6 +282,17 @@
         return ActivityInfo.isResizeableMode(mResizeMode) || mService.mForceResizableTasks;
     }
 
+    /**
+     * Tests if the orientation should be preserved upon user interactive resizig operations.
+
+     * @return true if orientation should not get changed upon resizing operation.
+     */
+    boolean preserveOrientationOnResize() {
+        return mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
+                || mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
+                || mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+    }
+
     boolean isOnTopLauncher() {
         return mIsOnTopLauncher;
     }
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 6887312..267566b 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -50,9 +50,9 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.MotionEvent;
-import android.view.SurfaceControl;
 import android.view.WindowManager;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.input.InputWindowHandle;
 import com.android.server.wm.WindowManagerService.H;
@@ -61,6 +61,7 @@
 import java.lang.annotation.RetentionPolicy;
 
 class TaskPositioner implements DimLayer.DimLayerUser {
+    private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
     private static final String TAG_LOCAL = "TaskPositioner";
     private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
 
@@ -89,6 +90,12 @@
 
     public static final int RESIZING_HINT_DURATION_MS = 0;
 
+    // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait).
+    // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever
+    // aspect he desires.
+    @VisibleForTesting
+    static final float MIN_ASPECT = 1.2f;
+
     private final WindowManagerService mService;
     private WindowPositionerEventReceiver mInputEventReceiver;
     private Display mDisplay;
@@ -103,8 +110,11 @@
 
     private Task mTask;
     private boolean mResizing;
+    private boolean mPreserveOrientation;
+    private boolean mStartOrientationWasLandscape;
     private final Rect mWindowOriginalBounds = new Rect();
     private final Rect mWindowDragBounds = new Rect();
+    private final Point mMaxVisibleSize = new Point();
     private float mStartDragX;
     private float mStartDragY;
     @CtrlType
@@ -226,6 +236,11 @@
         mService = service;
     }
 
+    @VisibleForTesting
+    Rect getWindowDragBounds() {
+        return mWindowDragBounds;
+    }
+
     /**
      * @param display The Display that the window being dragged is on.
      */
@@ -294,6 +309,7 @@
         mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
+        mDisplay.getRealSize(mMaxVisibleSize);
 
         mDragEnded = false;
     }
@@ -335,44 +351,57 @@
         mService.resumeRotationLocked();
     }
 
-    void startDragLocked(WindowState win, boolean resize, float startX, float startY) {
+    void startDrag(WindowState win, boolean resize, boolean preserveOrientation, float startX,
+                   float startY) {
         if (DEBUG_TASK_POSITIONING) {
-            Slog.d(TAG, "startDragLocked: win=" + win + ", resize=" + resize
-                + ", {" + startX + ", " + startY + "}");
+            Slog.d(TAG, "startDrag: win=" + win + ", resize=" + resize
+                    + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", "
+                    + startY + "}");
         }
-        mCtrlType = CTRL_NONE;
         mTask = win.getTask();
-        mStartDragX = startX;
-        mStartDragY = startY;
-
         // Use the dim bounds, not the original task bounds. The cursor
         // movement should be calculated relative to the visible bounds.
         // Also, use the dim bounds of the task which accounts for
         // multiple app windows. Don't use any bounds from win itself as it
         // may not be the same size as the task.
         mTask.getDimBounds(mTmpRect);
+        startDrag(resize, preserveOrientation, startX, startY, mTmpRect);
+    }
+
+    @VisibleForTesting
+    void startDrag(boolean resize, boolean preserveOrientation,
+                   float startX, float startY, Rect startBounds) {
+        mCtrlType = CTRL_NONE;
+        mStartDragX = startX;
+        mStartDragY = startY;
+        mPreserveOrientation = preserveOrientation;
 
         if (resize) {
-            if (startX < mTmpRect.left) {
+            if (startX < startBounds.left) {
                 mCtrlType |= CTRL_LEFT;
             }
-            if (startX > mTmpRect.right) {
+            if (startX > startBounds.right) {
                 mCtrlType |= CTRL_RIGHT;
             }
-            if (startY < mTmpRect.top) {
+            if (startY < startBounds.top) {
                 mCtrlType |= CTRL_TOP;
             }
-            if (startY > mTmpRect.bottom) {
+            if (startY > startBounds.bottom) {
                 mCtrlType |= CTRL_BOTTOM;
             }
-            mResizing = true;
+            mResizing = mCtrlType != CTRL_NONE;
         }
 
-        mWindowOriginalBounds.set(mTmpRect);
+        // In case of !isDockedInEffect we are using the union of all task bounds. These might be
+        // made up out of multiple windows which are only partially overlapping. When that happens,
+        // the orientation from the window of interest to the entire stack might diverge. However
+        // for now we treat them as the same.
+        mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
+        mWindowOriginalBounds.set(startBounds);
 
         // Make sure we always have valid drag bounds even if the drag ends before any move events
         // have been handled.
-        mWindowDragBounds.set(mTmpRect);
+        mWindowDragBounds.set(startBounds);
     }
 
     private void endDragLocked() {
@@ -387,26 +416,7 @@
         }
 
         if (mCtrlType != CTRL_NONE) {
-            // This is a resizing operation.
-            final int deltaX = Math.round(x - mStartDragX);
-            final int deltaY = Math.round(y - mStartDragY);
-            int left = mWindowOriginalBounds.left;
-            int top = mWindowOriginalBounds.top;
-            int right = mWindowOriginalBounds.right;
-            int bottom = mWindowOriginalBounds.bottom;
-            if ((mCtrlType & CTRL_LEFT) != 0) {
-                left = Math.min(left + deltaX, right - mMinVisibleWidth);
-            }
-            if ((mCtrlType & CTRL_TOP) != 0) {
-                top = Math.min(top + deltaY, bottom - mMinVisibleHeight);
-            }
-            if ((mCtrlType & CTRL_RIGHT) != 0) {
-                right = Math.max(left + mMinVisibleWidth, right + deltaX);
-            }
-            if ((mCtrlType & CTRL_BOTTOM) != 0) {
-                bottom = Math.max(top + mMinVisibleHeight, bottom + deltaY);
-            }
-            mWindowDragBounds.set(left, top, right, bottom);
+            resizeDrag(x, y);
             mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
             return false;
         }
@@ -428,6 +438,168 @@
         return false;
     }
 
+    /**
+     * The user is drag - resizing the window.
+     *
+     * @param x The x coordinate of the current drag coordinate.
+     * @param y the y coordinate of the current drag coordinate.
+     */
+    @VisibleForTesting
+    void resizeDrag(float x, float y) {
+        // This is a resizing operation.
+        // We need to keep various constraints:
+        // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y]
+        // 2. The orientation is kept - if required.
+        final int deltaX = Math.round(x - mStartDragX);
+        final int deltaY = Math.round(y - mStartDragY);
+        int left = mWindowOriginalBounds.left;
+        int top = mWindowOriginalBounds.top;
+        int right = mWindowOriginalBounds.right;
+        int bottom = mWindowOriginalBounds.bottom;
+
+        // The aspect which we have to respect. Note that if the orientation does not need to be
+        // preserved the aspect will be calculated as 1.0 which neutralizes the following
+        // computations.
+        final float minAspect = !mPreserveOrientation
+                ? 1.0f
+                : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT));
+        // Calculate the resulting width and height of the drag operation.
+        int width = right - left;
+        int height = bottom - top;
+        if ((mCtrlType & CTRL_LEFT) != 0) {
+            width = Math.max(mMinVisibleWidth, width - deltaX);
+        } else if ((mCtrlType & CTRL_RIGHT) != 0) {
+            width = Math.max(mMinVisibleWidth, width + deltaX);
+        }
+        if ((mCtrlType & CTRL_TOP) != 0) {
+            height = Math.max(mMinVisibleHeight, height - deltaY);
+        } else if ((mCtrlType & CTRL_BOTTOM) != 0) {
+            height = Math.max(mMinVisibleHeight, height + deltaY);
+        }
+
+        // If we have to preserve the orientation - check that we are doing so.
+        final float aspect = (float) width / (float) height;
+        if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT)
+                || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) {
+            // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
+            // drag axis. What ever is producing the bigger rectangle will be chosen.
+            int width1;
+            int width2;
+            int height1;
+            int height2;
+            if (mStartOrientationWasLandscape) {
+                // Assuming that the width is our target we calculate the height.
+                width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
+                height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT));
+                if (height1 < mMinVisibleHeight) {
+                    // If the resulting height is too small we adjust to the minimal size.
+                    height1 = mMinVisibleHeight;
+                    width1 = Math.max(mMinVisibleWidth,
+                            Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT)));
+                }
+                // Assuming that the height is our target we calculate the width.
+                height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
+                width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT));
+                if (width2 < mMinVisibleWidth) {
+                    // If the resulting width is too small we adjust to the minimal size.
+                    width2 = mMinVisibleWidth;
+                    height2 = Math.max(mMinVisibleHeight,
+                            Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT)));
+                }
+            } else {
+                // Assuming that the width is our target we calculate the height.
+                width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
+                height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT));
+                if (height1 < mMinVisibleHeight) {
+                    // If the resulting height is too small we adjust to the minimal size.
+                    height1 = mMinVisibleHeight;
+                    width1 = Math.max(mMinVisibleWidth,
+                            Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT)));
+                }
+                // Assuming that the height is our target we calculate the width.
+                height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
+                width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT));
+                if (width2 < mMinVisibleWidth) {
+                    // If the resulting width is too small we adjust to the minimal size.
+                    width2 = mMinVisibleWidth;
+                    height2 = Math.max(mMinVisibleHeight,
+                            Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT)));
+                }
+            }
+
+            // Use the bigger of the two rectangles if the major change was positive, otherwise
+            // do the opposite.
+            final boolean grows = width > (right - left) || height > (bottom - top);
+            if (grows == (width1 * height1 > width2 * height2)) {
+                width = width1;
+                height = height1;
+            } else {
+                width = width2;
+                height = height2;
+            }
+        }
+
+        // Update mWindowDragBounds to the new drag size.
+        updateDraggedBounds(left, top, right, bottom, width, height);
+    }
+
+    /**
+     * Given the old coordinates and the new width and height, update the mWindowDragBounds.
+     *
+     * @param left      The original left bound before the user started dragging.
+     * @param top       The original top bound before the user started dragging.
+     * @param right     The original right bound before the user started dragging.
+     * @param bottom    The original bottom bound before the user started dragging.
+     * @param newWidth  The new dragged width.
+     * @param newHeight The new dragged height.
+     */
+    void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth,
+                             int newHeight) {
+        // Generate the final bounds by keeping the opposite drag edge constant.
+        if ((mCtrlType & CTRL_LEFT) != 0) {
+            left = right - newWidth;
+        } else { // Note: The right might have changed - if we pulled at the right or not.
+            right = left + newWidth;
+        }
+        if ((mCtrlType & CTRL_TOP) != 0) {
+            top = bottom - newHeight;
+        } else { // Note: The height might have changed - if we pulled at the bottom or not.
+            bottom = top + newHeight;
+        }
+
+        mWindowDragBounds.set(left, top, right, bottom);
+
+        checkBoundsForOrientationViolations(mWindowDragBounds);
+    }
+
+    /**
+     * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set).
+     *
+     * @param bounds The bounds to be checked.
+     */
+    private void checkBoundsForOrientationViolations(Rect bounds) {
+        // When using debug check that we are not violating the given constraints.
+        if (DEBUG_ORIENTATION_VIOLATIONS) {
+            if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) {
+                Slog.e(TAG, "Orientation violation detected! should be "
+                        + (mStartOrientationWasLandscape ? "landscape" : "portrait")
+                        + " but is the other");
+            } else {
+                Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height());
+            }
+            if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) {
+                Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth
+                        + ", " + bounds.width() + ") Height(min,is)=("
+                        + mMinVisibleHeight + ", " + bounds.height() + ")");
+            }
+            if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) {
+                Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x
+                        + ", " + bounds.width() + ") Height(min,is)=("
+                        + mMaxVisibleSize.y + ", " + bounds.height() + ")");
+            }
+        }
+    }
+
     private void updateWindowDragBounds(int x, int y, Rect stackBounds) {
         final int offsetX = Math.round(x - mStartDragX);
         final int offsetY = Math.round(y - mStartDragY);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 04cf2e3..b3d1b13 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5726,7 +5726,8 @@
             win = windowForClientLocked(null, window, false);
             // win shouldn't be null here, pass it down to startPositioningLocked
             // to get warning if it's null.
-            if (!startPositioningLocked(win, false /*resize*/, startX, startY)) {
+            if (!startPositioningLocked(
+                        win, false /*resize*/, false /*preserveOrientation*/, startX, startY)) {
                 return false;
             }
         }
@@ -5741,8 +5742,8 @@
         synchronized (mWindowMap) {
             final Task task = displayContent.findTaskForResizePoint(x, y);
             if (task != null) {
-                if (!startPositioningLocked(
-                        task.getTopVisibleAppMainWindow(), true /*resize*/, x, y)) {
+                if (!startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/,
+                            task.preserveOrientationOnResize(), x, y)) {
                     return;
                 }
                 taskId = task.mTaskId;
@@ -5757,10 +5758,12 @@
         }
     }
 
-    private boolean startPositioningLocked(
-            WindowState win, boolean resize, float startX, float startY) {
-        if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "startPositioningLocked: "
-            + "win=" + win + ", resize=" + resize + ", {" + startX + ", " + startY + "}");
+    private boolean startPositioningLocked(WindowState win, boolean resize,
+            boolean preserveOrientation, float startX, float startY) {
+        if (DEBUG_TASK_POSITIONING)
+            Slog.d(TAG_WM, "startPositioningLocked: "
+                            + "win=" + win + ", resize=" + resize + ", preserveOrientation="
+                            + preserveOrientation + ", {" + startX + ", " + startY + "}");
 
         if (win == null || win.getAppToken() == null) {
             Slog.w(TAG_WM, "startPositioningLocked: Bad window " + win);
@@ -5801,7 +5804,7 @@
             return false;
         }
 
-        mTaskPositioner.startDragLocked(win, resize, startX, startY);
+        mTaskPositioner.startDrag(win, resize, preserveOrientation, startX, startY);
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5eab795..e2027fd 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2883,7 +2883,7 @@
         mShowToOwnerOnly = showToOwnerOnly;
     }
 
-    boolean isHiddenFromUserLocked() {
+    private boolean isHiddenFromUserLocked() {
         // Child windows are evaluated based on their parent window.
         final WindowState win = getTopParentWindow();
         if (win.mAttrs.type < FIRST_SYSTEM_WINDOW
@@ -3552,16 +3552,23 @@
 
     /** Returns the topmost parent window if this is a child of another window, else this. */
     WindowState getTopParentWindow() {
-        WindowState w = this;
-        while (w != null && w.mIsChildWindow) {
-            w = w.getParentWindow();
+        WindowState current = this;
+        WindowState topParent = current;
+        while (current != null && current.mIsChildWindow) {
+            current = current.getParentWindow();
+            // Parent window can be null if the child is detached from it's parent already, but
+            // someone still has a reference to access it. So, we return the top parent value we
+            // already have instead of null.
+            if (current != null) {
+                topParent = current;
+            }
         }
-        return w;
+        return topParent;
     }
 
     boolean isParentWindowHidden() {
         final WindowState parent = getParentWindow();
-        return (parent == null) ? false : parent.mHidden;
+        return parent != null && parent.mHidden;
     }
 
     void setWillReplaceWindow(boolean animate) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 45f3698..e82e7fb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3093,6 +3093,7 @@
                 // If admin is a device or profile owner tidy that up first.
                 if (isDeviceOwner(adminReceiver, userHandle)) {
                     clearDeviceOwnerLocked(getDeviceOwnerAdminLocked(), userHandle);
+                    clearDeviceOwnerUserRestrictionLocked(UserHandle.of(userHandle));
                 }
                 if (isProfileOwner(adminReceiver, userHandle)) {
                     final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver,
@@ -3108,6 +3109,15 @@
         }
     }
 
+    // It's temporary solution to clear DISALLOW_ADD_USER after CTS
+    // TODO: b/31952368 when the restriction is moved from system to the device owner,
+    // it can be removed.
+    private void clearDeviceOwnerUserRestrictionLocked(UserHandle userHandle) {
+        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) {
+            mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userHandle);
+        }
+    }
+
     /**
      * Return if a given package has testOnly="true", in which case we'll relax certain rules
      * for CTS.
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
new file mode 100644
index 0000000..0139671
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2012 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.server;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyListOf;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.Manifest.permission;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.INetworkScoreCache;
+import android.net.NetworkKey;
+import android.net.NetworkScoreManager;
+import android.net.NetworkScorerAppManager;
+import android.net.NetworkScorerAppManager.NetworkScorerAppData;
+import android.net.ScoredNetwork;
+import android.net.WifiKey;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.server.devicepolicy.MockUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link NetworkScoreService}.
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class NetworkScoreServiceTest {
+    private static final ScoredNetwork SCORED_NETWORK =
+            new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
+                    null /* rssiCurve*/);
+    private static final NetworkScorerAppData PREV_SCORER = new NetworkScorerAppData(
+            "prevPackageName", 0, "prevScorerName", null /* configurationActivityClassName */,
+            "prevScoringServiceClass");
+    private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData(
+            "newPackageName", 1, "newScorerName", null /* configurationActivityClassName */,
+            "newScoringServiceClass");
+
+    @Mock private PackageManager mPackageManager;
+    @Mock private NetworkScorerAppManager mNetworkScorerAppManager;
+    @Mock private Context mContext;
+    @Mock private Resources mResources;
+    @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2;
+    @Mock private IBinder mIBinder, mIBinder2;
+    @Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;
+
+    private ContentResolver mContentResolver;
+    private NetworkScoreService mNetworkScoreService;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mNetworkScoreCache.asBinder()).thenReturn(mIBinder);
+        when(mNetworkScoreCache2.asBinder()).thenReturn(mIBinder2);
+        mContentResolver = InstrumentationRegistry.getContext().getContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        when(mContext.getResources()).thenReturn(mResources);
+        mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager);
+    }
+
+    @Test
+    public void testSystemReady_networkScorerProvisioned() throws Exception {
+        Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 1);
+
+        mNetworkScoreService.systemReady();
+
+        verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString());
+    }
+
+    @Test
+    public void testSystemReady_networkScorerNotProvisioned_defaultScorer() throws Exception {
+        Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0);
+
+        when(mResources.getString(R.string.config_defaultNetworkScorerPackageName))
+                .thenReturn(NEW_SCORER.mPackageName);
+
+        mNetworkScoreService.systemReady();
+
+        verify(mNetworkScorerAppManager).setActiveScorer(NEW_SCORER.mPackageName);
+        assertEquals(1,
+                Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED));
+
+    }
+
+    @Test
+    public void testSystemReady_networkScorerNotProvisioned_noDefaultScorer() throws Exception {
+        Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0);
+
+        when(mResources.getString(R.string.config_defaultNetworkScorerPackageName))
+                .thenReturn(null);
+
+        mNetworkScoreService.systemReady();
+
+        verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString());
+        assertEquals(1,
+                Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED));
+    }
+
+    @Test
+    public void testSystemRunning() {
+        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
+
+        mNetworkScoreService.systemRunning();
+
+        verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
+                new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))),
+                any(ServiceConnection.class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.SYSTEM));
+    }
+
+    @Test
+    public void testUpdateScores_notActiveScorer() {
+        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
+
+        try {
+            mNetworkScoreService.updateScores(new ScoredNetwork[0]);
+            fail("SecurityException expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testUpdateScores_oneRegisteredCache() throws RemoteException {
+        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
+
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+        mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
+
+        verify(mNetworkScoreCache).updateScores(mScoredNetworkCaptor.capture());
+
+        assertEquals(1, mScoredNetworkCaptor.getValue().size());
+        assertEquals(SCORED_NETWORK, mScoredNetworkCaptor.getValue().get(0));
+    }
+
+    @Test
+    public void testUpdateScores_twoRegisteredCaches() throws RemoteException {
+        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
+
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.registerNetworkScoreCache(
+                NetworkKey.TYPE_WIFI, mNetworkScoreCache2);
+
+        // updateScores should update both caches
+        mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
+
+        verify(mNetworkScoreCache).updateScores(anyListOf(ScoredNetwork.class));
+        verify(mNetworkScoreCache2).updateScores(anyListOf(ScoredNetwork.class));
+
+        mNetworkScoreService.unregisterNetworkScoreCache(
+                NetworkKey.TYPE_WIFI, mNetworkScoreCache2);
+
+        // updateScores should only update the first cache since the 2nd has been unregistered
+        mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
+
+        verify(mNetworkScoreCache, times(2)).updateScores(anyListOf(ScoredNetwork.class));
+
+        mNetworkScoreService.unregisterNetworkScoreCache(
+                NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+        // updateScores should not update any caches since they are both unregistered
+        mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
+
+        verifyNoMoreInteractions(mNetworkScoreCache, mNetworkScoreCache2);
+    }
+
+    @Test
+    public void testClearScores_notActiveScorer_noBroadcastNetworkPermission() {
+        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
+        when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
+            .thenReturn(PackageManager.PERMISSION_DENIED);
+        try {
+            mNetworkScoreService.clearScores();
+            fail("SecurityException expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testClearScores_activeScorer_noBroadcastNetworkPermission() {
+        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
+        when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
+            .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        mNetworkScoreService.clearScores();
+    }
+
+    @Test
+    public void testClearScores_activeScorer() throws RemoteException {
+        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
+
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.clearScores();
+
+        verify(mNetworkScoreCache).clearScores();
+    }
+
+    @Test
+    public void testClearScores_notActiveScorer_hasBroadcastNetworkPermission()
+            throws RemoteException {
+        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
+        when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+        mNetworkScoreService.clearScores();
+
+        verify(mNetworkScoreCache).clearScores();
+    }
+
+    @Test
+    public void testSetActiveScorer_noScoreNetworksPermission() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(permission.SCORE_NETWORKS), anyString());
+
+        try {
+            mNetworkScoreService.setActiveScorer(null);
+            fail("SecurityException expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testSetActiveScorer_failure() throws RemoteException {
+        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER);
+        when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(false);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+        boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
+
+        assertFalse(success);
+        verify(mNetworkScoreCache).clearScores();
+        verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
+                new ComponentName(PREV_SCORER.mPackageName, PREV_SCORER.mScoringServiceClassName))),
+                any(ServiceConnection.class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.SYSTEM));
+    }
+
+    @Test
+    public void testSetActiveScorer_success() throws RemoteException {
+        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, NEW_SCORER);
+        when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(true);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+        boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
+
+        assertTrue(success);
+        verify(mNetworkScoreCache).clearScores();
+        verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
+                new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))),
+                any(ServiceConnection.class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.SYSTEM));
+        verify(mContext, times(2)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(NetworkScoreManager.ACTION_SCORER_CHANGED),
+                eq(UserHandle.SYSTEM));
+    }
+
+    @Test
+    public void testDisableScoring_notActiveScorer_noBroadcastNetworkPermission() {
+        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
+        when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        try {
+            mNetworkScoreService.disableScoring();
+            fail("SecurityException expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+    }
+
+    @Test
+    public void testDisableScoring_activeScorer() throws RemoteException {
+        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
+        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
+        when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+        mNetworkScoreService.disableScoring();
+
+        verify(mNetworkScoreCache).clearScores();
+        verify(mContext).sendBroadcastAsUser(
+                MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED)
+                        .setPackage(PREV_SCORER.mPackageName)),
+                eq(UserHandle.SYSTEM));
+        verify(mContext, never()).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+    }
+
+    @Test
+    public void testDisableScoring_notActiveScorer_hasBroadcastNetworkPermission()
+            throws RemoteException {
+        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
+        when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
+        when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
+        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+        mNetworkScoreService.disableScoring();
+
+        verify(mNetworkScoreCache).clearScores();
+        verify(mContext).sendBroadcastAsUser(
+                MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED)
+                        .setPackage(PREV_SCORER.mPackageName)),
+                eq(UserHandle.SYSTEM));
+        verify(mContext, never()).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+    }
+
+    @Test
+    public void testRegisterNetworkScoreCache_noBroadcastNetworkPermission() {
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(permission.BROADCAST_NETWORK_PRIVILEGED), anyString());
+
+        try {
+            mNetworkScoreService.registerNetworkScoreCache(
+                NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+            fail("SecurityException expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testUnregisterNetworkScoreCache_noBroadcastNetworkPermission() {
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(permission.BROADCAST_NETWORK_PRIVILEGED), anyString());
+
+        try {
+            mNetworkScoreService.unregisterNetworkScoreCache(
+                    NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+            fail("SecurityException expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDump_noDumpPermission() {
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(permission.DUMP), anyString());
+
+        try {
+            mNetworkScoreService.dump(
+                    new FileDescriptor(), new PrintWriter(new StringWriter()), new String[0]);
+            fail("SecurityException expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDump_doesNotCrash() {
+        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
+        StringWriter stringWriter = new StringWriter();
+
+        mNetworkScoreService.dump(
+                new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
+
+        assertFalse(stringWriter.toString().isEmpty());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 58db192..3806da6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -82,6 +82,21 @@
         return Mockito.argThat(m);
     }
 
+    public static Intent checkIntent(final Intent intent) {
+        final Matcher<Intent> m = new BaseMatcher<Intent>() {
+            @Override
+            public boolean matches(Object item) {
+                if (item == null) return false;
+                return intent.filterEquals((Intent) item);
+            }
+            @Override
+            public void describeTo(Description description) {
+                description.appendText(intent.toString());
+            }
+        };
+        return Mockito.argThat(m);
+    }
+
     public static Bundle checkUserRestrictions(String... keys) {
         final Bundle expected = DpmTestUtils.newRestrictions(Preconditions.checkNotNull(keys));
         final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
new file mode 100644
index 0000000..aec6dec
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2016 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.server.wm;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.graphics.Rect;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.server.wm.TaskPositioner.MIN_ASPECT;
+import static com.android.server.wm.WindowManagerService.dipToPixel;
+import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
+import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link TaskPositioner} class.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskPositionerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskPositionerTests extends WindowTestsBase {
+
+    private final boolean DEBUGGING = false;
+    private final String TAG = "TaskPositionerTest";
+
+    private final static int MOUSE_DELTA_X = 5;
+    private final static int MOUSE_DELTA_Y = 5;
+
+    private int mMinVisibleWidth;
+    private int mMinVisibleHeight;
+    private TaskPositioner mPositioner;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        final Display display = sDisplayContent.getDisplay();
+        final DisplayMetrics dm = new DisplayMetrics();
+        display.getMetrics(dm);
+
+        // This should be the same calculation as the TaskPositioner uses.
+        mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
+        mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
+
+        mPositioner = new TaskPositioner(sWm);
+        mPositioner.register(display);
+    }
+
+    /**
+     * This tests that free resizing will allow to change the orientation as well
+     * as does some basic tests (e.g. dragging in Y only will keep X stable).
+     */
+    @Test
+    public void testBasicFreeWindowResizing() throws Exception {
+        final Rect r = new Rect(100, 220, 700, 520);
+        final int midY = (r.top + r.bottom) / 2;
+
+        // Start a drag resize starting upper left.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+        // Drag to a good landscape size.
+        mPositioner.resizeDrag(0.0f, 0.0f);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a good portrait size.
+        mPositioner.resizeDrag(400.0f, 0.0f);
+        assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a too small size for the width.
+        mPositioner.resizeDrag(2000.0f, r.top);
+        assertBoundsEquals(
+                new Rect(r.right - mMinVisibleWidth, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a too small size for the height.
+        mPositioner.resizeDrag(r.left, 2000.0f);
+        assertBoundsEquals(
+                new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Start a drag resize left and see that only the left coord changes..
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+        // Drag to the left.
+        mPositioner.resizeDrag(0.0f, midY);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the right.
+        mPositioner.resizeDrag(200.0f, midY);
+        assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the top
+        mPositioner.resizeDrag(r.left, 0.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the bottom
+        mPositioner.resizeDrag(r.left, 1000.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that by dragging any edge, the fixed / opposite edge(s) remains anchored.
+     */
+    @Test
+    public void testFreeWindowResizingTestAllEdges() throws Exception {
+        final Rect r = new Rect(100, 220, 700, 520);
+        final int midX = (r.left + r.right) / 2;
+        final int midY = (r.top + r.bottom) / 2;
+
+        // Drag upper left.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(0.0f, 0.0f);
+        assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+        assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+        // Drag upper.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(0.0f, 0.0f);
+        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+        assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+        // Drag upper right.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(r.right + 100, 0.0f);
+        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+        assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+        assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+        // Drag right.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, midY, r);
+        mPositioner.resizeDrag(r.right + 100, 0.0f);
+        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+        assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+        // Drag bottom right.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/,
+                r.right + MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
+        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+        assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+        assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+        // Drag bottom.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, midX, r.bottom + MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
+        assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+        assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+        // Drag bottom left.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r);
+        mPositioner.resizeDrag(0.0f, r.bottom + 100);
+        assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+        assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+        // Drag left.
+        mPositioner.startDrag(true /*resizing*/,
+                false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midX, r);
+        mPositioner.resizeDrag(0.0f, r.bottom + 100);
+        assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+        assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+        assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+        assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+    }
+
+    /**
+     * This tests that a constrained landscape window will keep the aspect and do the
+     * right things upon resizing when dragged from the top left corner.
+     */
+    @Test
+    public void testLandscapePreservedWindowResizingDragTopLeft() throws Exception {
+        final Rect r = new Rect(100, 220, 700, 520);
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+        // Drag to a good landscape size.
+        mPositioner.resizeDrag(0.0f, 0.0f);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a good portrait size.
+        mPositioner.resizeDrag(400.0f, 0.0f);
+        int width = Math.round((float) (r.bottom - MOUSE_DELTA_Y) * MIN_ASPECT);
+        assertBoundsEquals(new Rect(r.right - width, MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a too small size for the width.
+        mPositioner.resizeDrag(2000.0f, r.top);
+        final int w = mMinVisibleWidth;
+        final int h = Math.round(w / MIN_ASPECT);
+        assertBoundsEquals(new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a too small size for the height.
+        mPositioner.resizeDrag(r.left, 2000.0f);
+        assertBoundsEquals(
+                new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that a constrained landscape window will keep the aspect and do the
+     * right things upon resizing when dragged from the left corner.
+     */
+    @Test
+    public void testLandscapePreservedWindowResizingDragLeft() throws Exception {
+        final Rect r = new Rect(100, 220, 700, 520);
+        final int midY = (r.top + r.bottom) / 2;
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+        // Drag to the left.
+        mPositioner.resizeDrag(0.0f, midY);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the right.
+        mPositioner.resizeDrag(200.0f, midY);
+        assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag all the way to the right and see the height also shrinking.
+        mPositioner.resizeDrag(2000.0f, midY);
+        final int w = mMinVisibleWidth;
+        final int h = Math.round((float)w / MIN_ASPECT);
+        assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the top.
+        mPositioner.resizeDrag(r.left, 0.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the bottom.
+        mPositioner.resizeDrag(r.left, 1000.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that a constrained landscape window will keep the aspect and do the
+     * right things upon resizing when dragged from the top corner.
+     */
+    @Test
+    public void testLandscapePreservedWindowResizingDragTop() throws Exception {
+        final Rect r = new Rect(100, 220, 700, 520);
+        final int midX = (r.left + r.right) / 2;
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+
+        // Drag to the left (no change).
+        mPositioner.resizeDrag(0.0f, r.top);
+        assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the right (no change).
+        mPositioner.resizeDrag(2000.0f, r.top);
+        assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the top.
+        mPositioner.resizeDrag(300.0f, 0.0f);
+        int h = r.bottom - MOUSE_DELTA_Y;
+        int w = Math.max(r.right - r.left, Math.round(h * MIN_ASPECT));
+        assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the bottom.
+        mPositioner.resizeDrag(r.left, 1000.0f);
+        h = mMinVisibleHeight;
+        assertBoundsEquals(new Rect(r.left, r.bottom - h, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that a constrained portrait window will keep the aspect and do the
+     * right things upon resizing when dragged from the top left corner.
+     */
+    @Test
+    public void testPortraitPreservedWindowResizingDragTopLeft() throws Exception {
+        final Rect r = new Rect(330, 100, 630, 600);
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+        assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+        // Drag to a good landscape size.
+        mPositioner.resizeDrag(0.0f, 0.0f);
+        int height = Math.round((float) (r.right - MOUSE_DELTA_X) * MIN_ASPECT);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.bottom - height, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a good portrait size.
+        mPositioner.resizeDrag(500.0f, 0.0f);
+        assertBoundsEquals(new Rect(500 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to a too small size for the height and the the width shrinking.
+        mPositioner.resizeDrag(r.left + MOUSE_DELTA_X, 2000.0f);
+        final int w = Math.max(mMinVisibleWidth, Math.round(mMinVisibleHeight / MIN_ASPECT));
+        final int h = Math.max(mMinVisibleHeight, Math.round(w * MIN_ASPECT));
+        assertBoundsEquals(
+                new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that a constrained portrait window will keep the aspect and do the
+     * right things upon resizing when dragged from the left corner.
+     */
+    @Test
+    public void testPortraitPreservedWindowResizingDragLeft() throws Exception {
+        final Rect r = new Rect(330, 100, 630, 600);
+        final int midY = (r.top + r.bottom) / 2;
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+        // Drag to the left.
+        mPositioner.resizeDrag(0.0f, midY);
+        int w = r.right - MOUSE_DELTA_X;
+        int h = Math.round(w * MIN_ASPECT);
+        assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.top + h),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the right.
+        mPositioner.resizeDrag(450.0f, midY);
+        assertBoundsEquals(new Rect(450 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag all the way to the right.
+        mPositioner.resizeDrag(2000.0f, midY);
+        w = mMinVisibleWidth;
+        h = Math.max(Math.round((float)w * MIN_ASPECT), r.height());
+        assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the top.
+        mPositioner.resizeDrag(r.left, 0.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the bottom.
+        mPositioner.resizeDrag(r.left, 1000.0f);
+        assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    /**
+     * This tests that a constrained portrait window will keep the aspect and do the
+     * right things upon resizing when dragged from the top corner.
+     */
+    @Test
+    public void testPortraitPreservedWindowResizingDragTop() throws Exception {
+        final Rect r = new Rect(330, 100, 630, 600);
+        final int midX = (r.left + r.right) / 2;
+
+        mPositioner.startDrag(true /*resizing*/,
+                true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+
+        // Drag to the left (no change).
+        mPositioner.resizeDrag(0.0f, r.top);
+        assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the right (no change).
+        mPositioner.resizeDrag(2000.0f, r.top);
+        assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the top.
+        mPositioner.resizeDrag(300.0f, 0.0f);
+        int h = r.bottom - MOUSE_DELTA_Y;
+        int w = Math.min(r.width(), Math.round(h / MIN_ASPECT));
+        assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
+                mPositioner.getWindowDragBounds());
+
+        // Drag to the bottom.
+        mPositioner.resizeDrag(r.left, 1000.0f);
+        h = Math.max(mMinVisibleHeight, Math.round(mMinVisibleWidth * MIN_ASPECT));
+        w = Math.round(h / MIN_ASPECT);
+        assertBoundsEquals(new Rect(r.left, r.bottom - h, r.left + w, r.bottom),
+                mPositioner.getWindowDragBounds());
+    }
+
+    private void assertBoundsEquals(Rect expected, Rect actual) {
+        if (DEBUGGING) {
+            if (!expected.equals(actual)) {
+                Log.e(TAG, "rect(" + actual.toString() + ") != isRect(" + actual.toString()
+                        + ") " + Log.getStackTraceString(new Throwable()));
+            }
+        }
+        assertEquals(expected.left, actual.left);
+        assertEquals(expected.right, actual.right);
+        assertEquals(expected.top, actual.top);
+        assertEquals(expected.bottom, actual.bottom);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index 69bfc8f..df35b7ee 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -116,6 +116,11 @@
         assertEquals(root, child1.getTopParentWindow());
         assertEquals(child1, child2.getParentWindow());
         assertEquals(root, child2.getTopParentWindow());
+
+        // Test case were child is detached from parent.
+        root.removeChild(child1);
+        assertEquals(child1, child1.getTopParentWindow());
+        assertEquals(child1, child2.getParentWindow());
     }
 
     @Test
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index a012082..177759e 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -28,6 +28,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CopyOnWriteArraySet;
@@ -453,6 +454,10 @@
      * @param conferenceableConnections The set of connections this connection can conference with.
      */
     public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
+        if (Objects.equals(mConferenceableConnections, conferenceableConnections)) {
+            return;
+        }
+
         clearConferenceableList();
         for (Connection c : conferenceableConnections) {
             // If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a0f5217..a652a0a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -658,6 +658,24 @@
     public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
 
     /**
+     * Determines whether a maximum size limit for IMS conference calls is enforced on the device.
+     * When {@code true}, IMS conference calls will be limited to at most
+     * {@link #KEY_IMS_CONFERENCE_SIZE_LIMIT_INT} participants.  When {@code false}, no attempt is made
+     * to limit the number of participants in a conference (the carrier will raise an error when an
+     * attempt is made to merge too many participants into a conference).
+     */
+    public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL =
+            "is_ims_conference_size_enforced_bool";
+
+    /**
+     * Determines the maximum number of participants the carrier supports for a conference call.
+     * This number is exclusive of the current device.  A conference between 3 devices, for example,
+     * would have a size limit of 2 participants.
+     * Enforced when {@link #KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL} is {@code true}.
+     */
+    public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
+
+    /**
      * Determines whether High Definition audio property is displayed in the dialer UI.
      * If {@code false}, remove the HD audio property from the connection so that HD audio related
      * UI is not displayed. If {@code true}, keep HD audio property as it is configured.
@@ -1232,6 +1250,8 @@
         sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
+        sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false);
+        sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
         sDefaults.putBoolean(KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL, true);
         sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
         sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
diff --git a/tools/layoutlib/bridge/src/android/view/SurfaceView.java b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
index 1e7dfbe..ebb2af4 100644
--- a/tools/layoutlib/bridge/src/android/view/SurfaceView.java
+++ b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.util.AttributeSet;
 
 /**
@@ -49,6 +50,19 @@
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
+    public boolean gatherTransparentRegion(Region region) {
+      return false;
+    }
+
+    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+    }
+
+    public void setZOrderOnTop(boolean onTop) {
+    }
+
+    public void setSecure(boolean isSecure) {
+    }
+
     public SurfaceHolder getHolder() {
         return mSurfaceHolder;
     }
diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/main.cpp
index 5435728..5b4ba04 100644
--- a/tools/streaming_proto/main.cpp
+++ b/tools/streaming_proto/main.cpp
@@ -94,7 +94,7 @@
  * Figure out the name of the file we are generating.
  */
 static string
-make_file_name(const FileDescriptorProto& file_descriptor)
+make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
 {
     string const package = make_java_package(file_descriptor);
     string result;
@@ -103,7 +103,7 @@
         result += '/';
     }
 
-    result += make_outer_class_name(file_descriptor);
+    result += class_name;
     result += ".java";
 
     return result;
@@ -320,10 +320,16 @@
 
 /**
  * Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
  */
 static void
-write_file(stringstream& text, const FileDescriptorProto& file_descriptor)
+write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+        const string& filename, bool generate_outer,
+        const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
 {
+    stringstream text;
+
     string const package_name = make_java_package(file_descriptor);
     string const outer_class_name = make_outer_class_name(file_descriptor);
 
@@ -338,27 +344,92 @@
     }
 
     // This bit of policy is android api rules specific: Raw proto classes
-    // must never be in the API, but they should all be available for testing.
+    // must never be in the API
     text << "/** @hide */" << endl;
-    text << "@android.annotation.TestApi" << endl;
+//    text << "@android.annotation.TestApi" << endl;
 
-    text << "public final class " << outer_class_name << " {" << endl;
-    text << endl;
+    if (generate_outer) {
+        text << "public final class " << outer_class_name << " {" << endl;
+        text << endl;
+    }
 
-    int N;
-    const string indented = indent_more("");
+    size_t N;
+    const string indented = generate_outer ? indent_more("") : string();
     
+    N = enums.size();
+    for (size_t i=0; i<N; i++) {
+        write_enum(text, enums[i], indented);
+    }
+
+    N = messages.size();
+    for (size_t i=0; i<N; i++) {
+        write_message(text, messages[i], indented);
+    }
+
+    if (generate_outer) {
+        text << "}" << endl;
+    }
+
+    CodeGeneratorResponse::File* file_response = response->add_file();
+    file_response->set_name(filename);
+    file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class.  Put all of the enums into the "outer" class.
+ */
+static void
+write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+{
+    // If there is anything to put in the outer class file, create one
+    if (file_descriptor.enum_type_size() > 0) {
+        vector<EnumDescriptorProto> enums;
+        int N = file_descriptor.enum_type_size();
+        for (int i=0; i<N; i++) {
+            enums.push_back(file_descriptor.enum_type(i));
+        }
+
+        vector<DescriptorProto> messages;
+
+        write_file(response, file_descriptor,
+                make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+                true, enums, messages);
+    }
+
+    // For each of the message types, make a file
+    int N = file_descriptor.message_type_size();
+    for (int i=0; i<N; i++) {
+        vector<EnumDescriptorProto> enums;
+
+        vector<DescriptorProto> messages;
+        messages.push_back(file_descriptor.message_type(i));
+
+        write_file(response, file_descriptor,
+                make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+                false, enums, messages);
+    }
+}
+
+static void
+write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+{
+    int N;
+
+    vector<EnumDescriptorProto> enums;
     N = file_descriptor.enum_type_size();
     for (int i=0; i<N; i++) {
-        write_enum(text, file_descriptor.enum_type(i), indented);
+        enums.push_back(file_descriptor.enum_type(i));
     }
 
+    vector<DescriptorProto> messages;
     N = file_descriptor.message_type_size();
     for (int i=0; i<N; i++) {
-        write_message(text, file_descriptor.message_type(i), indented);
+        messages.push_back(file_descriptor.message_type(i));
     }
 
-    text << "}" << endl;
+    write_file(response, file_descriptor,
+            make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+            true, enums, messages);
 }
 
 /**
@@ -383,16 +454,11 @@
     for (int i=0; i<N; i++) {
         const FileDescriptorProto& file_descriptor = request.proto_file(i);
         if (should_generate_for_file(request, file_descriptor.name())) {
-            // Generate the text
-            stringstream text;
-            write_file(text, file_descriptor);
-
-            // Put the text in the response
-            CodeGeneratorResponse::File* file_response = response.add_file();
-            file_response->set_name(make_file_name(file_descriptor));
-            file_response->set_content(text.str());
-
-            cerr << "writing file: " << file_response->name() << endl;
+            if (file_descriptor.options().java_multiple_files()) {
+                write_multiple_files(&response, file_descriptor);
+            } else {
+                write_single_file(&response, file_descriptor);
+            }
         }
     }